libbzip2-java-0.9.1/ 0000755 0001750 0001750 00000000000 11745741315 014103 5 ustar osallou osallou libbzip2-java-0.9.1/AUTHORS 0000644 0001750 0001750 00000000023 11745741314 015145 0 ustar osallou osallou Matthew J. Francis libbzip2-java-0.9.1/LICENCE 0000644 0001750 0001750 00000002115 11745741314 015066 0 ustar osallou osallou Copyright (c) 2010 Matthew J. Francis and Contributors of the jbzip2 Project Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. libbzip2-java-0.9.1/src/ 0000755 0001750 0001750 00000000000 11745741315 014672 5 ustar osallou osallou libbzip2-java-0.9.1/src/org/ 0000755 0001750 0001750 00000000000 11745741315 015461 5 ustar osallou osallou libbzip2-java-0.9.1/src/org/itadaki/ 0000755 0001750 0001750 00000000000 11745741315 017067 5 ustar osallou osallou libbzip2-java-0.9.1/src/org/itadaki/bzip2/ 0000755 0001750 0001750 00000000000 11745741315 020115 5 ustar osallou osallou libbzip2-java-0.9.1/src/org/itadaki/bzip2/CRC32.java 0000644 0001750 0001750 00000012100 11745741314 021525 0 ustar osallou osallou /* * Copyright (c) 2011 Matthew Francis * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.itadaki.bzip2; /** * A CRC32 calculator */ public final class CRC32 { /** * A static CRC lookup table */ private static final int 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 int crc = 0xffffffff; /** * @return The current CRC */ public int getCRC() { return ~this.crc; } /** * Update the CRC with a single byte * @param value The value to update the CRC with */ public void updateCRC (final int value) { final int crc = this.crc; this.crc = (crc << 8) ^ crc32Lookup[((crc >> 24) ^ value) & 0xff]; } /** * Update the CRC with a sequence of identical bytes * @param value The value to update the CRC with * @param count The number of bytes */ public void updateCRC (final int value, int count) { int crc = this.crc; while (count-- > 0) { crc = (crc << 8) ^ crc32Lookup[((crc >> 24) ^ value) & 0xff]; } this.crc = crc; } } libbzip2-java-0.9.1/src/org/itadaki/bzip2/BZip2DivSufSort.java 0000644 0001750 0001750 00000147564 11745741314 023717 0 ustar osallou osallou /* * Copyright (c) 2003-2008 Yuta Mori All Rights Reserved. * Copyright (c) 2011 Matthew Francis * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.itadaki.bzip2; /** * 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. */ public class BZip2DivSufSort { /** */ private static final int STACK_SIZE = 64; /** */ private static final int BUCKET_A_SIZE = 256; /** */ private static final int BUCKET_B_SIZE = 65536; /** */ private static final int SS_BLOCKSIZE = 1024; /** */ private static final int INSERTIONSORT_THRESHOLD = 8; /** */ private static final 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 final int[] SA; /** */ private final byte[] T; /** */ private final int n; /** * @param array1 * @param index1 * @param array2 * @param index2 */ private static final void swapElements (final int[] array1, final int index1, final int[] array2, final int index2) { final int temp = array1[index1]; array1[index1] = array2[index2]; array2[index2] = temp; } /** * @param p1 * @param p2 * @param depth * @return */ private int ssCompare (final int p1, final int p2, final int depth) { final int[] SA = this.SA; final byte[] T = this.T; final int U1n, U2n; // pointers within T int U1, U2; for ( U1 = depth + SA[p1], U2 = depth + SA[p2], U1n = SA[p1 + 1] + 2, U2n = SA[p2 + 1] + 2; (U1 < U1n) && (U2 < U2n) && (T[U1] == T[U2]); ++U1, ++U2 ); return U1 < U1n ? (U2 < U2n ? (T[U1] & 0xff) - (T[U2] & 0xff) : 1) : (U2 < U2n ? -1 : 0); } /** * @param PA * @param p1 * @param p2 * @param depth * @param size * @return */ private int ssCompareLast (int PA, int p1, int p2, int depth, int size) { final int[] SA = this.SA; final byte[] T = this.T; int U1, U2, U1n, U2n; for ( U1 = depth + SA[p1], U2 = depth + SA[p2], U1n = size, U2n = SA[(p2 + 1)] + 2; (U1 < U1n) && (U2 < U2n) && (T[U1] == T[U2]); ++U1, ++U2 ); if (U1 < U1n) { return (U2 < U2n) ? (T[U1] & 0xff) - (T[U2] & 0xff) : 1; } else if (U2 == U2n) { return 1; } for ( U1 = U1 % size, U1n = SA[PA] + 2; (U1 < U1n) && (U2 < U2n) && (T[U1] == T[U2]); ++U1, ++U2 ); return U1 < U1n ? (U2 < U2n ? (T[U1] & 0xff) - (T[U2] & 0xff) : 1) : (U2 < U2n ? -1 : 0); } /** * @param PA * @param first * @param last * @param depth */ private void ssInsertionSort (int PA, int first, int last, int depth) { final int[] SA = this.SA; int i, j; // pointer within SA int t; int r; for (i = last - 2; first <= i; --i) { for (t = SA[i], j = i + 1; 0 < (r = ssCompare (PA + t, PA + SA[j], depth));) { do { SA[j - 1] = SA[j]; } while ((++j < last) && (SA[j] < 0)); if (last <= j) { break; } } if (r == 0) { SA[j] = ~SA[j]; } SA[j - 1] = t; } } /** * @param Td * @param PA * @param sa * @param i * @param size */ private void ssFixdown (int Td, int PA, int sa, int i, int size) { final int[] SA = this.SA; final byte[] T = this.T; int j, k; int v; int c, d, e; for (v = SA[sa + i], c = (T[Td + SA[PA + v]]) & 0xff; (j = 2 * i + 1) < size; SA[sa + i] = SA[sa + k], i = k) { d = T[Td + SA[PA + SA[sa + (k = j++)]]] & 0xff; if (d < (e = T[Td + SA[PA + SA[sa + j]]] & 0xff)) { k = j; d = e; } if (d <= c) break; } SA[sa + i] = v; } /** * @param Td * @param PA * @param sa * @param size */ private void ssHeapSort (int Td, int PA, int sa, int size) { final int[] SA = this.SA; final byte[] T = this.T; int i, m; int t; m = size; if ((size % 2) == 0) { m--; if ((T[Td + SA[PA + SA[sa + (m / 2)]]] & 0xff) < (T[Td + SA[PA + SA[sa + m]]] & 0xff)) { swapElements (SA, sa + m, SA, sa + (m / 2)); } } for (i = m / 2 - 1; 0 <= i; --i) { ssFixdown (Td, PA, sa, i, m); } if ((size % 2) == 0) { swapElements (SA, sa, SA, sa + m); ssFixdown (Td, PA, sa, 0, m); } for (i = m - 1; 0 < i; --i) { t = SA[sa]; SA[sa] = SA[sa + i]; ssFixdown (Td, PA, sa, 0, i); SA[sa + i] = t; } } /** * @param Td * @param PA * @param v1 * @param v2 * @param v3 * @return */ private int ssMedian3 (final int Td, final int PA, int v1, int v2, int v3) { final int[] SA = this.SA; final byte[] T = this.T; int T_v1 = T[Td + SA[PA + SA[v1]]] & 0xff; int T_v2 = T[Td + SA[PA + SA[v2]]] & 0xff; int T_v3 = T[Td + SA[PA + SA[v3]]] & 0xff; if (T_v1 > T_v2) { final int temp = v1; v1 = v2; v2 = temp; final int T_vtemp = T_v1; T_v1 = T_v2; T_v2 = T_vtemp; } if (T_v2 > T_v3) { if (T_v1 > T_v3) { return v1; } return v3; } return v2; } /** * @param Td * @param PA * @param v1 * @param v2 * @param v3 * @param v4 * @param v5 * @return */ private int ssMedian5 (final int Td, final int PA, int v1, int v2, int v3, int v4, int v5) { final int[] SA = this.SA; final byte[] T = this.T; int T_v1 = T[Td + SA[PA + SA[v1]]] & 0xff; int T_v2 = T[Td + SA[PA + SA[v2]]] & 0xff; int T_v3 = T[Td + SA[PA + SA[v3]]] & 0xff; int T_v4 = T[Td + SA[PA + SA[v4]]] & 0xff; int T_v5 = T[Td + SA[PA + SA[v5]]] & 0xff; int temp; int T_vtemp; 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; } if (T_v3 > T_v4) { return v4; } return v3; } /** * @param Td * @param PA * @param first * @param last * @return */ private int ssPivot (final int Td, final int PA, final int first, final int last) { int middle; int t; t = last - first; middle = first + t / 2; if (t <= 512) { if (t <= 32) { return ssMedian3 (Td, PA, first, middle, last - 1); } t >>= 2; return ssMedian5 (Td, PA, first, first + t, middle, last - 1 - t, last - 1); } t >>= 3; return ssMedian3 ( Td, PA, ssMedian3 (Td, PA, first, first + t, first + (t << 1)), ssMedian3 (Td, PA, middle - t, middle, middle + t), ssMedian3 (Td, PA, last - 1 - (t << 1), last - 1 - t, last - 1) ); } /** * @param n * @return */ private int ssLog (final int n) { return ((n & 0xff00) != 0) ? 8 + log2table[(n >> 8) & 0xff] : log2table[n & 0xff]; } /** * @param PA * @param first * @param last * @param depth * @return */ private int ssSubstringPartition (final int PA, final int first, final int last, final int depth) { final int[] SA = this.SA; int a, b; int t; for (a = first - 1, b = last;;) { for (; (++a < b) && ((SA[PA + SA[a]] + depth) >= (SA[PA + SA[a] + 1] + 1));) { SA[a] = ~SA[a]; } for (; (a < --b) && ((SA[PA + SA[b]] + depth) < (SA[PA + SA[b] + 1] + 1));); if (b <= a) { break; } t = ~SA[b]; SA[b] = SA[a]; SA[a] = t; } if (first < a) { SA[first] = ~SA[first]; } return a; } /** */ private static class StackEntry { /** */ final int a; /** */ final int b; /** */ final int c; /** */ final int d; /** * @param a * @param b * @param c * @param d */ public StackEntry (final int a, final int b, final int c, final int d) { this.a = a; this.b = b; this.c = c; this.d = d; } } /** * @param PA * @param first * @param last * @param depth */ private void ssMultiKeyIntroSort (final int PA, int first, int last, int depth) { final int[] SA = this.SA; final byte[] T = this.T; final StackEntry[] stack = new StackEntry[STACK_SIZE]; int Td = 0; int a = 0, b = 0, c = 0, d = 0, e = 0, f = 0; int s = 0, t = 0; int ssize; int limit; int v = 0, x = 0; for (ssize = 0, limit = ssLog (last - first);;) { if ((last - first) <= INSERTIONSORT_THRESHOLD) { if (1 < (last - first)) { ssInsertionSort (PA, first, last, depth); } if (ssize == 0) return; StackEntry entry = stack[--ssize]; first = entry.a; last = entry.b; depth = entry.c; limit = entry.d; continue; } Td = depth; if (limit-- == 0) { ssHeapSort (Td, PA, first, last - first); } if (limit < 0) { for (a = first + 1, v = T[Td + SA[PA + SA[first]]] & 0xff; a < last; ++a) { if ((x = (T[Td + SA[PA + SA[a]]] & 0xff)) != v) { if (1 < (a - first)) { break; } v = x; first = a; } } if ((T[Td + SA[PA + SA[first]] - 1] & 0xff) < v) { first = 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 = ssPivot (Td, PA, first, last); v = T[Td + SA[PA + SA[a]]] & 0xff; swapElements (SA, first, SA, a); for (b = first; (++b < last) && ((x = (T[Td + SA[PA + SA[b]]] & 0xff)) == v);); if (((a = b) < last) && (x < v)) { for (; (++b < last) && ((x = (T[Td + SA[PA + SA[b]]] & 0xff)) <= v);) { if (x == v) { swapElements (SA, b, SA, a); ++a; } } } for (c = last; (b < --c) && ((x = (T[Td + SA[PA + SA[c]]] & 0xff)) == v);); if ((b < (d = c)) && (x > v)) { for (; (b < --c) && ((x = (T[Td + SA[PA + SA[c]]] & 0xff)) >= v);) { if (x == v) { swapElements (SA, c, SA, d); --d; } } } for (; b < c;) { swapElements (SA, b, SA, c); for (; (++b < c) && ((x = (T[Td + SA[PA + SA[b]]] & 0xff)) <= v);) { if (x == v) { swapElements (SA, b, SA, a); ++a; } } for (; (b < --c) && ((x = (T[Td + SA[PA + SA[c]]] & 0xff)) >= v);) { if (x == v) { swapElements (SA, c, SA, d); --d; } } } if (a <= d) { c = b - 1; if ((s = a - first) > (t = b - a)) { s = t; } for (e = first, f = b - s; 0 < s; --s, ++e, ++f) { swapElements (SA, e, SA, f); } if ((s = d - c) > (t = last - d - 1)) { s = t; } for (e = b, f = last - s; 0 < s; --s, ++e, ++f) { swapElements (SA, e, SA, f); } a = first + (b - a); c = last - (d - c); b = (v <= (T[Td + SA[PA + SA[a]] - 1] & 0xff)) ? a : 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 ((T[Td + SA[PA + SA[first]] - 1] & 0xff) < v) { first = ssSubstringPartition (PA, first, last, depth); limit = ssLog (last - first); } depth += 1; } } } /** * @param array1 * @param first1 * @param array2 * @param first2 * @param size */ private void ssBlockSwap (final int[] array1, final int first1, final int[] array2, final int first2, final int size) { int a, b; int i; for (i = size, a = first1, b = first2; 0 < i; --i, ++a, ++b) { swapElements (array1, a, array2, b); } } /** * @param PA * @param buf * @param bufoffset * @param first * @param middle * @param last * @param depth */ private void ssMergeForward (final int PA, int[] buf, final int bufoffset, final int first, final int middle, final int last, final int depth) { final int[] SA = this.SA; int bufend; int i, j, k; int t; int r; bufend = bufoffset + (middle - first) - 1; ssBlockSwap (buf, bufoffset, SA, first, middle - first); for (t = SA[first], i = first, j = bufoffset, k = middle;;) { r = ssCompare (PA + buf[j], PA + SA[k], depth); if (r < 0) { do { SA[i++] = buf[j]; if (bufend <= j) { buf[j] = t; return; } buf[j++] = SA[i]; } while (buf[j] < 0); } else if (r > 0) { do { SA[i++] = SA[k]; SA[k++] = SA[i]; if (last <= k) { while (j < bufend) { SA[i++] = buf[j]; buf[j++] = SA[i]; } SA[i] = buf[j]; buf[j] = t; return; } } while (SA[k] < 0); } else { SA[k] = ~SA[k]; do { SA[i++] = buf[j]; if (bufend <= j) { buf[j] = t; return; } buf[j++] = SA[i]; } while (buf[j] < 0); do { SA[i++] = SA[k]; SA[k++] = SA[i]; if (last <= k) { while (j < bufend) { SA[i++] = buf[j]; buf[j++] = SA[i]; } SA[i] = buf[j]; buf[j] = t; return; } } while (SA[k] < 0); } } } /** * @param PA * @param buf * @param buf * @param bufoffset * @param first * @param middle * @param last * @param depth */ private void ssMergeBackward (final int PA, int[] buf, final int bufoffset, final int first, final int middle, final int last, final int depth) { final int[] SA = this.SA; int p1, p2; int bufend; int i, j, k; int t; int r; int x; bufend = bufoffset + (last - middle); ssBlockSwap (buf, bufoffset, SA, middle, last - middle); x = 0; if (buf[bufend - 1] < 0) { x |= 1; p1 = PA + ~buf[bufend - 1]; } else { p1 = PA + buf[bufend - 1]; } if (SA[middle - 1] < 0) { x |= 2; p2 = PA + ~SA[middle - 1]; } else { p2 = PA + SA[middle - 1]; } for (t = SA[last - 1], i = last - 1, j = bufend - 1, k = middle - 1;;) { r = ssCompare (p1, p2, depth); if (r > 0) { if ((x & 1) != 0) { do { SA[i--] = buf[j]; buf[j--] = SA[i]; } while (buf[j] < 0); x ^= 1; } SA[i--] = buf[j]; if (j <= bufoffset) { buf[j] = t; return; } buf[j--] = 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 { SA[i--] = SA[k]; SA[k--] = SA[i]; } while (SA[k] < 0); x ^= 2; } SA[i--] = SA[k]; SA[k--] = SA[i]; if (k < first) { while (bufoffset < j) { SA[i--] = buf[j]; buf[j--] = SA[i]; } SA[i] = buf[j]; buf[j] = t; return; } if (SA[k] < 0) { x |= 2; p2 = PA + ~SA[k]; } else { p2 = PA + SA[k]; } } else { if ((x & 1) != 0) { do { SA[i--] = buf[j]; buf[j--] = SA[i]; } while (buf[j] < 0); x ^= 1; } SA[i--] = ~buf[j]; if (j <= bufoffset) { buf[j] = t; return; } buf[j--] = SA[i]; if ((x & 2) != 0) { do { SA[i--] = SA[k]; SA[k--] = SA[i]; } while (SA[k] < 0); x ^= 2; } SA[i--] = SA[k]; SA[k--] = SA[i]; if (k < first) { while (bufoffset < j) { SA[i--] = buf[j]; buf[j--] = SA[i]; } SA[i] = buf[j]; buf[j] = t; return; } if (buf[j] < 0) { x |= 1; p1 = PA + ~buf[j]; } else { p1 = PA + buf[j]; } if (SA[k] < 0) { x |= 2; p2 = PA + ~SA[k]; } else { p2 = PA + SA[k]; } } } } /** * @param a * @return */ private final static int getIDX (final int a) { return (0 <= a) ? a : ~a; } /** * @param PA * @param depth * @param a */ private void ssMergeCheckEqual (final int PA, final int depth, final int a) { final int[] SA = this.SA; if ( (0 <= SA[a]) && (ssCompare (PA + getIDX (SA[a - 1]), PA + SA[a], depth) == 0) ) { SA[a] = ~SA[a]; } } /** * @param PA * @param first * @param middle * @param last * @param buf * @param bufoffset * @param bufsize * @param depth */ private void ssMerge (final int PA, int first, int middle, int last, int[] buf, final int bufoffset, final int bufsize, final int depth) { final int[] SA = this.SA; final StackEntry[] stack = new StackEntry[STACK_SIZE]; int i, j; int m, len, half; int ssize; int check, next; for (check = 0, ssize = 0;;) { if ((last - middle) <= bufsize) { if ((first < middle) && (middle < last)) { ssMergeBackward (PA, buf, bufoffset, first, middle, last, depth); } if ((check & 1) != 0) { ssMergeCheckEqual (PA, depth, first); } if ((check & 2) != 0) { ssMergeCheckEqual (PA, depth, last); } if (ssize == 0) return; StackEntry entry = stack[--ssize]; first = entry.a; middle = entry.b; last = entry.c; check = entry.d; continue; } if ((middle - first) <= bufsize) { if (first < middle) { ssMergeForward ( PA, buf, bufoffset, first, middle, last, depth); } if ((check & 1) != 0) { ssMergeCheckEqual (PA, depth, first); } if ((check & 2) != 0) { ssMergeCheckEqual (PA, depth, last); } if (ssize == 0) return; StackEntry entry = stack[--ssize]; first = entry.a; middle = entry.b; last = entry.c; check = entry.d; continue; } for ( m = 0, len = Math.min (middle - first, last - middle), half = len >> 1; 0 < len; len = half, half >>= 1 ) { if (ssCompare (PA + getIDX (SA[middle + m + half]), PA + getIDX (SA[middle - m - half - 1]), depth) < 0) { m += half + 1; half -= (len & 1) ^ 1; } } if (0 < m) { ssBlockSwap (SA, middle - m, SA, middle, m); i = j = middle; next = 0; if ((middle + m) < last) { if (SA[middle + m] < 0) { for (; SA[i - 1] < 0; --i); SA[middle + m] = ~SA[middle + m]; } for (j = middle; 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) { ssMergeCheckEqual (PA, depth, first); } ssMergeCheckEqual (PA, depth, middle); if ((check & 2) != 0) { ssMergeCheckEqual (PA, depth, last); } if (ssize == 0) return; StackEntry entry = stack[--ssize]; first = entry.a; middle = entry.b; last = entry.c; check = entry.d; } } } /** * @param PA * @param first * @param last * @param buf * @param bufoffset * @param bufsize * @param depth * @param lastsuffix * @param size */ private void subStringSort (final int PA, int first, final int last, final int[] buf, final int bufoffset, final int bufsize, final int depth, final boolean lastsuffix, final int size) { final int[] SA = this.SA; int a, b; int[] curbuf; int curbufoffset; int i, j, k; int curbufsize; if (lastsuffix) { ++first; } for (a = first, i = 0; (a + SS_BLOCKSIZE) < last; a += SS_BLOCKSIZE, ++i) { ssMultiKeyIntroSort (PA, a, a + SS_BLOCKSIZE, depth); curbuf = SA; curbufoffset = a + SS_BLOCKSIZE; curbufsize = last - (a + SS_BLOCKSIZE); if (curbufsize <= bufsize) { curbufsize = bufsize; curbuf = buf; curbufoffset = bufoffset; } for (b = a, k = SS_BLOCKSIZE, j = i; (j & 1) != 0; b -= k, k <<= 1, j >>>= 1) { ssMerge (PA, b - k, b, b + k, curbuf, curbufoffset, curbufsize, depth); } } ssMultiKeyIntroSort (PA, a, last, depth); for (k = SS_BLOCKSIZE; i != 0; k <<= 1, i >>= 1) { if ((i & 1) != 0) { ssMerge (PA, a - k, a, last, buf, bufoffset, bufsize, depth); a -= k; } } if (lastsuffix) { int r; for ( a = first, i = SA[first - 1], r = 1; (a < last) && ((SA[a] < 0) || (0 < (r = ssCompareLast (PA, PA + i, PA + SA[a], depth, size)))); ++a ) { SA[a - 1] = SA[a]; } if (r == 0) { SA[a] = ~SA[a]; } SA[a - 1] = i; } } /*----------------------------------------------------------------------------*/ /** * @param ISA * @param ISAd * @param ISAn * @param p * @return */ private int trGetC (final int ISA, final int ISAd, final int ISAn, final int p) { return (((ISAd + p) < ISAn) ? this.SA[ISAd + p] : this.SA[ISA + ((ISAd - ISA + p) % (ISAn - ISA))]); } /** * @param ISA * @param ISAd * @param ISAn * @param sa * @param i * @param size */ private void trFixdown (final int ISA, final int ISAd, final int ISAn, final int sa, int i, final int size) { final int[] SA = this.SA; int j, k; int v; int c, d, e; for (v = SA[sa +i], c = trGetC (ISA, ISAd, ISAn, v); (j = 2 * i + 1) < size; SA[sa + i] = SA[sa + k], i = k) { k = j++; d = trGetC (ISA, ISAd, ISAn, SA[sa + k]); if (d < (e = trGetC (ISA, ISAd, ISAn, SA[sa + j]))) { k = j; d = e; } if (d <= c) { break; } } SA[sa + i] = v; } /** * @param ISA * @param ISAd * @param ISAn * @param sa * @param size */ private void trHeapSort (final int ISA, final int ISAd, final int ISAn, final int sa, final int size) { final int[] SA = this.SA; int i, m; int t; m = size; if ((size % 2) == 0) { m--; if (trGetC (ISA, ISAd, ISAn, SA[sa + (m / 2)]) < trGetC (ISA, ISAd, ISAn, SA[sa + m])) { swapElements (SA, sa + m, SA, sa + (m / 2)); } } for (i = m / 2 - 1; 0 <= i; --i) { trFixdown (ISA, ISAd, ISAn, sa, i, m); } if ((size % 2) == 0) { swapElements (SA, sa + 0, SA, sa + m); trFixdown (ISA, ISAd, ISAn, sa, 0, m); } for (i = m - 1; 0 < i; --i) { t = SA[sa + 0]; SA[sa + 0] = SA[sa + i]; trFixdown (ISA, ISAd, ISAn, sa, 0, i); SA[sa + i] = t; } } /** * @param ISA * @param ISAd * @param ISAn * @param first * @param last */ private void trInsertionSort (final int ISA, final int ISAd, final int ISAn, int first, int last) { final int[] SA = this.SA; int a, b; int t, r; for (a = first + 1; a < last; ++a) { for (t = SA[a], b = a - 1; 0 > (r = trGetC (ISA, ISAd, ISAn, t) - trGetC (ISA, ISAd, ISAn, SA[b]));) { do { SA[b + 1] = SA[b]; } while ((first <= --b) && (SA[b] < 0)); if (b < first) { break; } } if (r == 0) { SA[b] = ~SA[b]; } SA[b + 1]= t; } } /** * @param n * @return */ private int trLog (int n) { return ((n & 0xffff0000) != 0) ? (((n & 0xff000000) != 0) ? 24 + log2table[(n >> 24) & 0xff] : 16 + log2table[(n >> 16) & 0xff]) : (((n & 0x0000ff00) != 0) ? 8 + log2table[(n >> 8) & 0xff] : 0 + log2table[(n >> 0) & 0xff]); } /** * @param ISA * @param ISAd * @param ISAn * @param v1 * @param v2 * @param v3 * @return */ private int trMedian3 (final int ISA, final int ISAd, final int ISAn, int v1, int v2, int v3) { final int[] SA = this.SA; int SA_v1 = trGetC (ISA, ISAd, ISAn, SA[v1]); int SA_v2 = trGetC (ISA, ISAd, ISAn, SA[v2]); int SA_v3 = trGetC (ISA, ISAd, ISAn, SA[v3]); if (SA_v1 > SA_v2) { final int temp = v1; v1 = v2; v2 = temp; final int SA_vtemp = SA_v1; SA_v1 = SA_v2; SA_v2 = SA_vtemp; } if (SA_v2 > SA_v3) { if (SA_v1 > SA_v3) { return v1; } return v3; } return v2; } /** * @param ISA * @param ISAd * @param ISAn * @param v1 * @param v2 * @param v3 * @param v4 * @param v5 * @return */ private int trMedian5 (final int ISA, final int ISAd, final int ISAn, int v1, int v2, int v3, int v4, int v5) { final int[] SA = this.SA; int SA_v1 = trGetC (ISA, ISAd, ISAn, SA[v1]); int SA_v2 = trGetC (ISA, ISAd, ISAn, SA[v2]); int SA_v3 = trGetC (ISA, ISAd, ISAn, SA[v3]); int SA_v4 = trGetC (ISA, ISAd, ISAn, SA[v4]); int SA_v5 = trGetC (ISA, ISAd, ISAn, SA[v5]); int temp; int SA_vtemp; 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; } if (SA_v3 > SA_v4) { return v4; } return v3; } /** * @param ISA * @param ISAd * @param ISAn * @param first * @param last * @return */ private int trPivot (final int ISA, final int ISAd, final int ISAn, final int first, final int last) { final int middle; int t; t = last - first; middle = first + t / 2; if (t <= 512) { if (t <= 32) { return trMedian3 (ISA, ISAd, ISAn, first, middle, last - 1); } t >>= 2; return trMedian5 ( ISA, ISAd, ISAn, first, first + t, middle, last - 1 - t, last - 1 ); } t >>= 3; return trMedian3 ( ISA, ISAd, ISAn, trMedian3 (ISA, ISAd, ISAn, first, first + t, first + (t << 1)), trMedian3 (ISA, ISAd, ISAn, middle - t, middle, middle + t), trMedian3 (ISA, ISAd, ISAn, last - 1 - (t << 1), last - 1 - t, last - 1) ); } /*---------------------------------------------------------------------------*/ /** * @param ISA * @param sa * @param first * @param last */ private void lsUpdateGroup (final int ISA, final int first, final int last) { final int[] SA = this.SA; int a, b; int t; for (a = first; a < last; ++a) { if (0 <= SA[a]) { b = a; do { SA[ISA + SA[a]] = a; } while ((++a < last) && (0 <= SA[a])); SA[b] = b - a; if (last <= a) { break; } } b = a; do { SA[a] = ~SA[a]; } while (SA[++a] < 0); t = a; do { SA[ISA + SA[b]] = t; } while (++b <= a); } } /** * @param ISA * @param ISAd * @param ISAn * @param sa * @param first * @param last */ private void lsIntroSort (final int ISA, final int ISAd, final int ISAn, int first, int last) { final int[] SA = this.SA; final StackEntry[] stack = new StackEntry[STACK_SIZE]; int a, b, c, d, e, f; int s, t; int limit; int v, x = 0; int ssize; for (ssize = 0, limit = trLog (last - first);;) { if ((last - first) <= INSERTIONSORT_THRESHOLD) { if (1 < (last - first)) { trInsertionSort (ISA, ISAd, ISAn, first, last); lsUpdateGroup (ISA, first, last); } else if ((last - first) == 1) { SA[first] = -1; } if (ssize == 0) return; StackEntry entry = stack[--ssize]; first = entry.a; last = entry.b; limit = entry.c; continue; } if (limit-- == 0) { trHeapSort (ISA, ISAd, ISAn, first, last - first); for (a = last - 1; first < a; a = b) { for ( x = trGetC (ISA, ISAd, ISAn, SA[a]), b = a - 1; (first <= b) && (trGetC (ISA, ISAd, ISAn, SA[b]) == x); --b ) { SA[b] = ~SA[b]; } } lsUpdateGroup (ISA, first, last); if (ssize == 0) return; StackEntry entry = stack[--ssize]; first = entry.a; last = entry.b; limit = entry.c; continue; } a = trPivot (ISA, ISAd, ISAn, first, last); swapElements (SA, first, SA, a); v = trGetC (ISA, ISAd, ISAn, SA[first]); for (b = first; (++b < last) && ((x = trGetC (ISA, ISAd, ISAn, SA[b])) == v);); if (((a = b) < last) && (x < v)) { for (; (++b < last) && ((x = trGetC (ISA, ISAd, ISAn, SA[b])) <= v);) { if (x == v) { swapElements (SA, b, SA, a); ++a; } } } for (c = last; (b < --c) && ((x = trGetC (ISA, ISAd, ISAn, SA[c])) == v);); if ((b < (d = c)) && (x > v)) { for (; (b < --c) && ((x = trGetC (ISA, ISAd, ISAn, SA[c])) >= v);) { if (x == v) { swapElements (SA, c, SA, d); --d; } } } for (; b < c;) { swapElements (SA, b, SA, c); for (; (++b < c) && ((x = trGetC (ISA, ISAd, ISAn, SA[b])) <= v);) { if (x == v) { swapElements (SA, b, SA, a); ++a; } } for (; (b < --c) && ((x = trGetC (ISA, ISAd, ISAn, SA[c])) >= v);) { if (x == v) { swapElements (SA, c, SA, d); --d; } } } if (a <= d) { c = b - 1; if ((s = a - first) > (t = b - a)) { s = t; } for (e = first, f = b - s; 0 < s; --s, ++e, ++f) { swapElements (SA, e, SA, f); } if ((s = d - c) > (t = last - d - 1)) { s = t; } for (e = b, f = last - s; 0 < s; --s, ++e, ++f) { swapElements (SA, e, SA, f); } a = first + (b - a); b = last - (d - c); for (c = first, v = a - 1; c < a; ++c) { SA[ISA + SA[c]] = v; } if (b < last) { for (c = a, v = b - 1; c < b; ++c) { SA[ISA + SA[c]] = v; } } if ((b - a) == 1) { 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; StackEntry entry = stack[--ssize]; first = entry.a; last = entry.b; limit = entry.c; } } } /** * @param ISA * @param n * @param depth */ private void lsSort (final int ISA, final int n, final int depth) { final int[] SA = this.SA; int ISAd; int first, last, i; int t, skip; for (ISAd = ISA + depth; -n < SA[0]; ISAd += (ISAd - ISA)) { first = 0; skip = 0; do { if ((t = SA[first]) < 0) { first -= t; skip += t; } else { if (skip != 0) { SA[first + skip] = skip; skip = 0; } last = SA[ISA + t] + 1; lsIntroSort (ISA, ISAd, ISA + n, first, last); first = last; } } while (first < n); if (skip != 0) { SA[first + skip] = skip; } if (n < (ISAd - ISA)) { first = 0; do { if ((t = SA[first]) < 0) { first -= t; } else { last = SA[ISA + t] + 1; for (i = first; i < last; ++i) { SA[ISA + SA[i]] = i; } first = last; } } while (first < n); break; } } } /*---------------------------------------------------------------------------*/ /** */ private final class PartitionResult { /** */ final int first; /** */ final int last; /** * @param first * @param last */ public PartitionResult (final int first, final int last) { this.first = first; this.last = last; } } /** * @param ISA * @param ISAd * @param ISAn * @param first * @param last * @param v * @return */ private PartitionResult trPartition (final int ISA, final int ISAd, final int ISAn, int first, int last, final int v) { final int[] SA = this.SA; int a, b, c, d, e, f; int t, s; int x = 0; for (b = first - 1; (++b < last) && ((x = trGetC (ISA, ISAd, ISAn, SA[b])) == v);); if (((a = b) < last) && (x < v)) { for (; (++b < last) && ((x = trGetC (ISA, ISAd, ISAn, SA[b])) <= v);) { if (x == v) { swapElements (SA, b, SA, a); ++a; } } } for (c = last; (b < --c) && ((x = trGetC (ISA, ISAd, ISAn, SA[c])) == v);); if ((b < (d = c)) && (x > v)) { for (; (b < --c) && ((x = trGetC (ISA, ISAd, ISAn, SA[c])) >= v);) { if (x == v) { swapElements (SA, c, SA, d); --d; } } } for (; b < c;) { swapElements (SA, b, SA, c); for (; (++b < c) && ((x = trGetC (ISA, ISAd, ISAn, SA[b])) <= v);) { if (x == v) { swapElements (SA, b, SA, a); ++a; } } for (; (b < --c) && ((x = trGetC (ISA, ISAd, ISAn, SA[c])) >= v);) { if (x == v) { swapElements (SA, c, SA, d); --d; } } } if (a <= d) { c = b - 1; if ((s = a - first) > (t = b - a)) { s = t; } for (e = first, f = b - s; 0 < s; --s, ++e, ++f) { swapElements (SA, e, SA, f); } if ((s = d - c) > (t = last - d - 1)) { s = t; } for (e = b, f = last - s; 0 < s; --s, ++e, ++f) { swapElements (SA, e, SA, f); } first += (b - a); last -= (d - c); } return new PartitionResult (first, last); } /** * @param ISA * @param ISAn * @param first * @param a * @param b * @param last * @param depth */ private void trCopy (final int ISA, final int ISAn, final int first, final int a, final int b, final int last, final int depth) { final int[] SA = this.SA; int c, d, e; int s, v; v = b - 1; for (c = first, d = a - 1; c <= d; ++c) { if ((s = SA[c] - depth) < 0) { s += ISAn - ISA; } if (SA[ISA +s] == v) { SA[++d] = s; SA[ISA +s] = d; } } for (c = last - 1, e = d + 1, d = b; e < d; --c) { if ((s = SA[c] - depth) < 0) { s += ISAn - ISA; } if (SA[ISA +s] == v) { SA[--d] = s; SA[ISA + s] = d; } } } /** * @param ISA * @param ISAd * @param ISAn * @param first * @param last * @param budget * @param size */ private void trIntroSort (final int ISA, int ISAd, int ISAn, int first, int last, final TRBudget budget, final int size) { final int[] SA = this.SA; final StackEntry[] stack = new StackEntry[STACK_SIZE]; int a, b, c, d, e, f; int s, t; int v, x = 0; int limit, next; int ssize; for (ssize = 0, limit = trLog (last - first);;) { if (limit < 0) { if (limit == -1) { if (!budget.update (size, last - first)) break; PartitionResult result = 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) { SA[ISA + SA[c]] = v; } } if (b < last) { for (c = a, v = b - 1; c < b; ++c) { SA[ISA + 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; StackEntry 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; StackEntry entry = stack[--ssize]; ISAd = entry.a; first = entry.b; last = entry.c; limit = entry.d; } } } else { for (c = first; c < last; ++c) { SA[ISA + SA[c]] = c; } if (ssize == 0) return; StackEntry 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; trCopy (ISA, ISAn, first, a, b, last, ISAd - ISA); if (ssize == 0) return; StackEntry entry = stack[--ssize]; ISAd = entry.a; first = entry.b; last = entry.c; limit = entry.d; } else { if (0 <= SA[first]) { a = first; do { SA[ISA + SA[a]] = a; } while ((++a < last) && (0 <= SA[a])); first = a; } if (first < last) { a = first; do { SA[a] = ~SA[a]; } while (SA[++a] < 0); next = (SA[ISA + SA[a]] != SA[ISAd + SA[a]]) ? trLog (a - first + 1) : -1; if (++a < last) { for (b = first, v = a - 1; b < a; ++b) { SA[ISA + 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; StackEntry entry = stack[--ssize]; ISAd = entry.a; first = entry.b; last = entry.c; limit = entry.d; } } continue; } if ((last - first) <= INSERTIONSORT_THRESHOLD) { if (!budget.update (size, last - first)) break; trInsertionSort (ISA, ISAd, ISAn, first, last); limit = -3; continue; } if (limit-- == 0) { if (!budget.update (size, last - first)) break; trHeapSort (ISA, ISAd, ISAn, first, last - first); for (a = last - 1; first < a; a = b) { for ( x = trGetC (ISA, ISAd, ISAn, SA[a]), b = a - 1; (first <= b) && (trGetC (ISA, ISAd, ISAn, SA[b]) == x); --b ) { SA[b] = ~SA[b]; } } limit = -3; continue; } a = trPivot (ISA, ISAd, ISAn, first, last); swapElements (SA, first, SA, a); v = trGetC (ISA, ISAd, ISAn, SA[first]); for (b = first; (++b < last) && ((x = trGetC (ISA, ISAd, ISAn, SA[b])) == v);); if (((a = b) < last) && (x < v)) { for (; (++b < last) && ((x = trGetC (ISA, ISAd, ISAn, SA[b])) <= v);) { if (x == v) { swapElements (SA, b, SA, a); ++a; } } } for (c = last; (b < --c) && ((x = trGetC (ISA, ISAd, ISAn, SA[c])) == v);); if ((b < (d = c)) && (x > v)) { for (; (b < --c) && ((x = trGetC (ISA, ISAd, ISAn, SA[c])) >= v);) { if (x == v) { swapElements (SA, c, SA, d); --d; } } } for (; b < c;) { swapElements (SA, b, SA, c); for (; (++b < c) && ((x = trGetC (ISA, ISAd, ISAn, SA[b])) <= v);) { if (x == v) { swapElements (SA, b, SA, a); ++a; } } for (; (b < --c) && ((x = trGetC (ISA, ISAd, ISAn, SA[c])) >= v);) { if (x == v) { swapElements (SA, c, SA, d); --d; } } } if (a <= d) { c = b - 1; if ((s = a - first) > (t = b - a)) { s = t; } for (e = first, f = b - s; 0 < s; --s, ++e, ++f) { swapElements (SA, e, SA, f); } if ((s = d - c) > (t = last - d - 1)) { s = t; } for (e = b, f = last - s; 0 < s; --s, ++e, ++f) { swapElements (SA, e, SA, f); } a = first + (b - a); b = last - (d - c); next = (SA[ISA + SA[a]] != v) ? trLog (b - a) : -1; for (c = first, v = a - 1; c < a; ++c) { SA[ISA + SA[c]] = v; } if (b < last) { for (c = a, v = b - 1; c < b; ++c) { SA[ISA + 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; StackEntry 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) { lsUpdateGroup (ISA, stack[s].b, stack[s].c); } } } /** */ private static class TRBudget { /** */ int budget; /** */ int chance; /** * @param size * @param n * @return something */ public boolean update (final int size, final int n) { this.budget -= n; if (this.budget <= 0) { if (--this.chance == 0) { return false; } this.budget += size; } return true; } /** * @param budget * @param chance */ public TRBudget (final int budget, final int chance) { this.budget = budget; this.chance = chance; } } /** * @param ISA * @param n * @param depth */ private void trSort (final int ISA, final int n, final int depth) { final int[] SA = this.SA; int first = 0, last; int t; if (-n < SA[0]) { TRBudget budget = new TRBudget (n, trLog (n) * 2 / 3 + 1); do { if ((t = SA[first]) < 0) { first -= t; } else { last = SA[ISA + t] + 1; if (1 < (last - first)) { trIntroSort (ISA, ISA + depth, ISA + n, first, last, budget, n); if (budget.chance == 0) { /* Switch to Larsson-Sadakane sorting algorithm. */ if (0 < first) { SA[0] = -first; } lsSort (ISA, n, depth); break; } } first = last; } } while (first < n); } } /*---------------------------------------------------------------------------*/ /** * @param bucketB * @param c0 * @param c1 * @return */ private static final int BUCKET_B (final int c0, final int c1) { return (c1 << 8) | c0; } /** * @param bucketB * @param c0 * @param c1 * @return */ private static final int BUCKET_BSTAR (final int c0, final int c1) { return (c0 << 8) | c1; } /** * @param bucketA * @param bucketB * @return */ private int sortTypeBstar (final int[] bucketA, final int[] bucketB) { final byte[] T = this.T; final int[] SA = this.SA; final int n = this.n; final int[] tempbuf = new int[256]; int[] buf; int PAb, ISAb, bufoffset; int i, j, k, t, m, bufsize; int c0, c1; int flag; for (i = 1, flag = 1; i < n; ++i) { if (T[i - 1] != T[i]) { if ((T[i - 1] & 0xff) > (T[i] & 0xff)) { flag = 0; } break; } } i = n - 1; m = n; int ti,ti1, t0; if (((ti = (T[i] & 0xff)) < (t0 = (T[0] & 0xff))) || ((T[i] == T[0]) && (flag != 0))) { if (flag == 0) { ++bucketB[BUCKET_BSTAR (ti, t0)]; SA[--m] = i; } else { ++bucketB[BUCKET_B (ti, t0)]; } for (--i; (0 <= i) && ((ti = (T[i] & 0xff)) <= (ti1 = (T[i + 1] & 0xff))); --i) { ++bucketB[BUCKET_B (ti, ti1)]; } } for (; 0 <= i;) { do { ++bucketA[T[i] & 0xff]; } while ((0 <= --i) && ((T[i] & 0xff) >= (T[i + 1] & 0xff))); if (0 <= i) { ++bucketB[BUCKET_BSTAR (T[i] & 0xff, T[i + 1] & 0xff)]; SA[--m] = i; for (--i; (0 <= i) && ((ti = (T[i] & 0xff)) <= (ti1 = (T[i + 1] & 0xff))); --i) { ++bucketB[BUCKET_B (ti, ti1)]; } } } m = n - m; if (m == 0) { for (i = 0; i < n; ++i) { 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)]; } } PAb = n - m; ISAb = m; for (i = m - 2; 0 <= i; --i) { t = SA[PAb + i]; c0 = T[t] & 0xff; c1 = T[t + 1] & 0xff; SA[--bucketB[BUCKET_BSTAR (c0, c1)]] = i; } t = SA[PAb + m - 1]; c0 = T[t] & 0xff; c1 = T[t + 1] & 0xff; SA[--bucketB[BUCKET_BSTAR (c0, c1)]] = m - 1; buf = SA; bufoffset = m; bufsize = 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)) { subStringSort (PAb, i, j, buf, bufoffset, bufsize, 2, SA[i] == (m - 1), n); } } } for (i = m - 1; 0 <= i; --i) { if (0 <= SA[i]) { j = i; do { SA[ISAb + SA[i]] = i; } while ((0 <= --i) && (0 <= SA[i])); SA[i + 1] = i - j; if (i <= 0) { break; } } j = i; do { SA[ISAb + (SA[i] = ~SA[i])] = j; } while (SA[--i] < 0); SA[ISAb + SA[i]] = j; } trSort (ISAb, m, 1); i = n - 1; j = m; if (((T[i] & 0xff) < (T[0] & 0xff)) || ((T[i] == T[0]) && (flag != 0))) { if (flag == 0) { SA[SA[ISAb + --j]] = i; } for (--i; (0 <= i) && ((T[i] & 0xff) <= (T[i + 1] & 0xff)); --i); } for (; 0 <= i;) { for (--i; (0 <= i) && ((T[i] & 0xff) >= (T[i + 1] & 0xff)); --i); if (0 <= i) { SA[SA[ISAb + --j]] = i; for (--i; (0 <= i) && ((T[i] & 0xff) <= (T[i + 1] & 0xff)); --i); } } for (c0 = 255, i = 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) { SA[i] = 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; } /** * @param bucketA * @param bucketB * @return */ private int constructBWT (final int[] bucketA, final int[] bucketB) { final byte[] T = this.T; final int[] SA = this.SA; final int n = this.n; int i, j, t = 0; int s, s1; int c0 = 0, c1, c2 = 0; int orig = -1; for (c1 = 254; 0 <= c1; --c1) { for ( i = bucketB[BUCKET_BSTAR (c1, c1 + 1)], j = bucketA[c1 + 1], t = 0, c2 = -1; i <= j; --j ) { if (0 <= (s1 = s = SA[j])) { if (--s < 0) { s = n - 1; } if ((c0 = (T[s] & 0xff)) <= c1) { SA[j] = ~s1; if ((0 < s) && ((T[s - 1] & 0xff) > c0)) { s = ~s; } if (c2 == c0) { SA[--t] = s; } else { if (0 <= c2) { bucketB[BUCKET_B (c2, c1)] = t; } SA[t = bucketB[BUCKET_B (c2 = c0, c1)] - 1] = s; } } } else { SA[j] = ~s; } } } for (i = 0; i < n; ++i) { if (0 <= (s1 = s = SA[i])) { if (--s < 0) { s = n - 1; } if ((c0 = (T[s] & 0xff)) >= (T[s + 1] & 0xff)) { if ((0 < s) && ((T[s - 1] & 0xff) < c0)) { s = ~s; } if (c0 == c2) { SA[++t] = s; } else { if (c2 != -1) // BUGFIX: Original code can write to bucketA[-1] bucketA[c2] = t; SA[t = bucketA[c2 = c0] + 1] = s; } } } else { s1 = ~s1; } if (s1 == 0) { SA[i] = T[n - 1]; orig = i; } else { SA[i] = T[s1 - 1]; } } return orig; } /** * Performs a Burrows Wheeler Transform on the input array * @return the index of the first character of the input array within the output array */ public int bwt() { final int[] SA = this.SA; final byte[] T = this.T; final int n = this.n; final int[] bucketA = new int[BZip2DivSufSort.BUCKET_A_SIZE]; final int[] bucketB = new int[BZip2DivSufSort.BUCKET_B_SIZE]; if (n == 0) { return 0; } else if (n == 1) { SA[0] = T[0]; return 0; } int m = sortTypeBstar (bucketA, bucketB); if (0 < m) { return constructBWT (bucketA, bucketB); } return 0; } /** * @param T The input array * @param SA The output array * @param n The length of the input data */ public BZip2DivSufSort (final byte[] T, final int[] SA, final int n) { this.T = T; this.SA = SA; this.n = n; } } libbzip2-java-0.9.1/src/org/itadaki/bzip2/BZip2HuffmanStageDecoder.java 0000644 0001750 0001750 00000015053 11745741314 025470 0 ustar osallou osallou /* * Copyright (c) 2011 Matthew Francis * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.itadaki.bzip2; import java.io.IOException; /** * A decoder for the BZip2 Huffman coding stage */ public class BZip2HuffmanStageDecoder { /** * The BitInputStream from which Huffman codes are read */ private final BitInputStream bitInputStream; /** * The Huffman table number to use for each group of 50 symbols */ private final byte[] selectors; /** * The minimum code length for each Huffman table */ private final int[] minimumLengths = new int[BZip2Constants.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 final int[][] codeBases = new int[BZip2Constants.HUFFMAN_MAXIMUM_TABLES][BZip2Constants.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 final int[][] codeLimits = new int[BZip2Constants.HUFFMAN_MAXIMUM_TABLES][BZip2Constants.HUFFMAN_DECODE_MAXIMUM_CODE_LENGTH + 1]; /** * A mapping for each Huffman table from canonical code index to output symbol */ private final int[][] codeSymbols = new int[BZip2Constants.HUFFMAN_MAXIMUM_TABLES][BZip2Constants.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; /** * Constructs Huffman decoding tables from lists of Canonical Huffman code lengths * @param alphabetSize The total number of codes (uniform for each table) * @param tableCodeLengths The Canonical Huffman code lengths for each table */ private void createHuffmanDecodingTables (final int alphabetSize, final byte[][] tableCodeLengths) { for (int table = 0; table < tableCodeLengths.length; table++) { final int[] tableBases = this.codeBases[table]; final int[] tableLimits = this.codeLimits[table]; final int[] tableSymbols = this.codeSymbols[table]; final byte[] codeLengths = tableCodeLengths[table]; int minimumLength = BZip2Constants.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 (codeLengths[i], maximumLength); minimumLength = Math.min (codeLengths[i], minimumLength); } this.minimumLengths[table] = minimumLength; // Calculate the first output symbol for each code length for (int i = 0; i < alphabetSize; i++) { tableBases[codeLengths[i] + 1]++; } for (int i = 1; i < BZip2Constants.HUFFMAN_DECODE_MAXIMUM_CODE_LENGTH + 2; i++) { tableBases[i] += tableBases[i - 1]; } // Calculate the first and last Huffman code for each code length (codes at a given // length are sequential in value) int code = 0; for (int i = minimumLength; i <= maximumLength; i++) { int base = code; code += tableBases[i + 1] - tableBases[i]; tableBases[i] = base - tableBases[i]; tableLimits[i] = code - 1; code <<= 1; } // Populate the mapping from canonical code index to output symbol int codeIndex = 0; for (int bitLength = minimumLength; bitLength <= maximumLength; bitLength++) { for (int symbol = 0; symbol < alphabetSize; symbol++) { if (codeLengths[symbol] == bitLength) { tableSymbols[codeIndex++] = symbol; } } } } } /** * Decodes and returns the next symbol * @return The decoded symbol * @throws IOException if the end of the input stream is reached while decoding */ public int nextSymbol() throws IOException { final BitInputStream bitInputStream = this.bitInputStream; // Move to next group selector if required if (((++this.groupPosition % BZip2Constants.HUFFMAN_GROUP_RUN_LENGTH) == 0)) { this.currentTable = this.selectors[++this.groupIndex] & 0xff; } final int currentTable = this.currentTable; final int[] tableLimits = this.codeLimits[currentTable]; int codeLength = this.minimumLengths[currentTable]; // Starting with the minimum bit length for the table, read additional bits one at a time // until a complete code is recognised int codeBits = bitInputStream.readBits (codeLength); for (; codeLength <= BZip2Constants.HUFFMAN_DECODE_MAXIMUM_CODE_LENGTH; codeLength++) { if (codeBits <= tableLimits[codeLength]) { // Convert the code to a symbol index and return return this.codeSymbols[currentTable][codeBits - this.codeBases[currentTable][codeLength]]; } codeBits = (codeBits << 1) | bitInputStream.readBits (1); } throw new IOException ("Error decoding BZip2 block"); } /** * @param bitInputStream The BitInputStream from which Huffman codes are read * @param alphabetSize The total number of codes (uniform for each table) * @param tableCodeLengths The Canonical Huffman code lengths for each table * @param selectors The Huffman table number to use for each group of 50 symbols */ public BZip2HuffmanStageDecoder (final BitInputStream bitInputStream, final int alphabetSize, final byte[][] tableCodeLengths, final byte[] selectors) { this.bitInputStream = bitInputStream; this.selectors = selectors; this.currentTable = this.selectors[0]; createHuffmanDecodingTables (alphabetSize, tableCodeLengths); } } libbzip2-java-0.9.1/src/org/itadaki/bzip2/BZip2InputStream.java 0000644 0001750 0001750 00000020760 11745741314 024106 0 ustar osallou osallou /* * Copyright (c) 2011 Matthew Francis * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.itadaki.bzip2; import java.io.IOException; import java.io.InputStream; /** *
An InputStream wrapper that decompresses BZip2 data
* *A BZip2 stream consists of one or more blocks of compressed data. This decompressor reads a * whole block at a time, then progressively returns decompressed output.
* *On encountering any error decoding the compressed stream, an IOException is thrown, and * further reads will return {@code -1}
* *Note: Each BZip2 compressed block contains a CRC code which is verified after the block * has been read completely. If verification fails, an exception is thrown on the final read from * the block, potentially after corrupt data has already been returned. The compressed stream * also contains a final CRC code which is verified once the end of the stream has been reached. * This check may fail even if every individual block in the stream passes CRC verification. * If this possibility is of concern, you should read and store the entire decompressed stream * before further processing.
* *Instances of this class are not threadsafe.
*/ public class BZip2InputStream extends InputStream { /** * The stream from which compressed BZip2 data is read and decoded */ private InputStream inputStream; /** * An InputStream wrapper that provides bit-level reads */ private BitInputStream bitInputStream; /** * If {@code true}, the caller is assumed to have read away the stream's leading "BZ" identifier * bytes */ private final boolean headerless; /** * (@code true} if the end of the compressed stream has been reached, otherwise {@code false} */ private boolean streamComplete = false; /** * 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 int streamBlockSize; /** * The merged CRC of all blocks decompressed so far */ private int streamCRC = 0; /** * The decompressor for the current block */ private BZip2BlockDecompressor blockDecompressor = null; /* (non-Javadoc) * @see java.io.InputStream#read() */ @Override public int read() throws IOException { int nextByte = -1; if (this.blockDecompressor == null) { initialiseStream(); } else { nextByte = this.blockDecompressor.read(); } if (nextByte == -1) { if (initialiseNextBlock()) { nextByte = this.blockDecompressor.read(); } } return nextByte; } /* (non-Javadoc) * @see java.io.InputStream#read(byte[], int, int) */ @Override public int read (final byte[] destination, final int offset, final int length) throws IOException { int bytesRead = -1; if (this.blockDecompressor == null) { initialiseStream(); } else { bytesRead = this.blockDecompressor.read (destination, offset, length); } if (bytesRead == -1) { if (initialiseNextBlock()) { bytesRead = this.blockDecompressor.read (destination, offset, length); } } return bytesRead; } /* (non-Javadoc) * @see java.io.InputStream#close() */ @Override public void close() throws IOException { if (this.bitInputStream != null) { this.streamComplete = true; this.blockDecompressor = null; this.bitInputStream = null; try { this.inputStream.close(); } finally { this.inputStream = null; } } } /** * Reads the stream header and checks that the data appears to be a valid BZip2 stream * @throws IOException if the stream header is not valid */ private void initialiseStream() throws IOException { /* If the stream has been explicitly closed, throw an exception */ if (this.bitInputStream == 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 { int marker1 = this.headerless ? 0 : this.bitInputStream.readBits (16); int marker2 = this.bitInputStream.readBits (8); int blockSize = (this.bitInputStream.readBits (8) - '0'); if ( (!this.headerless && (marker1 != BZip2Constants.STREAM_START_MARKER_1)) || (marker2 != BZip2Constants.STREAM_START_MARKER_2) || (blockSize < 1) || (blockSize > 9)) { throw new IOException ("Invalid BZip2 header"); } this.streamBlockSize = blockSize * 100000; } catch (IOException e) { // If the stream header could not be parsed, stop trying to read more data this.streamComplete = true; throw e; } } /** * 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 * @return {@code true} if a block was successfully initialised, or {@code false} if the end of * file marker was encountered * @throws IOException 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 boolean initialiseNextBlock() throws IOException { /* 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) { int blockCRC = this.blockDecompressor.checkCRC(); this.streamCRC = ((this.streamCRC << 1) | (this.streamCRC >>> 31)) ^ blockCRC; } /* Read block-header or end-of-stream marker */ int marker1 = this.bitInputStream.readBits (24); int 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 e) { // If the block could not be decoded, stop trying to read more data this.streamComplete = true; throw e; } return true; } else if (marker1 == BZip2Constants.STREAM_END_MARKER_1 && marker2 == BZip2Constants.STREAM_END_MARKER_2) { // Read and verify the end-of-stream CRC this.streamComplete = true; int storedCombinedCRC = this.bitInputStream.readInteger(); 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"); } /** * @param inputStream The InputStream to wrap * @param headerless If {@code true}, the caller is assumed to have read away the stream's * leading "BZ" identifier bytes */ public BZip2InputStream (final InputStream inputStream, final boolean headerless) { if (inputStream == null) { throw new IllegalArgumentException ("Null input stream"); } this.inputStream = inputStream; this.bitInputStream = new BitInputStream (inputStream); this.headerless = headerless; } } libbzip2-java-0.9.1/src/org/itadaki/bzip2/HuffmanAllocator.java 0000644 0001750 0001750 00000015151 11745741314 024207 0 ustar osallou osallou /* * Copyright (c) 2011 Matthew Francis * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.itadaki.bzip2; /** * 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/) */ public class HuffmanAllocator { /** * FIRST() function * @param array The code length array * @param i The input position * @param nodesToMove The number of internal nodes to be relocated * @return The smallest {@code k} such that {@code nodesToMove <= k <= i} and * {@code i <= (array[k] % array.length)} */ private static int first (final int[] array, int i, final int nodesToMove) { final int length = array.length; final 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)) { int temp = (i + k) >> 1; if ((array[temp] % length) > limit) { k = temp; } else { i = temp; } } return k; } /** * Fills the code array with extended parent pointers * @param array The code length array */ private static void setExtendedParentPointers (final int[] array) { final 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 * @param array The code length array * @param maximumLength The maximum bit length for the generated codes * @return The number of nodes to relocate */ private static int findNodesToRelocate (final int[] array, final int maximumLength) { int currentNode = array.length - 2; for (int 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 * @param array The code length array */ private static void allocateNodeLengths (final int[] array) { int firstNode = array.length - 2; int nextNode = array.length - 1; for (int currentDepth = 1, availableNodes = 2; availableNodes > 0; currentDepth++) { final int lastNode = firstNode; firstNode = first (array, lastNode - 1, 0); for (int 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 * @param array The code length array * @param nodesToMove The number of internal nodes to be relocated * @param insertDepth The depth at which to insert relocated nodes */ private static void allocateNodeLengthsWithRelocation (final int[] array, final int nodesToMove, final 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++) { final 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 (int i = availableNodes - (lastNode - firstNode + offset); i > 0; i--) { array[nextNode--] = currentDepth; } nodesLeftToMove -= offset; availableNodes = (lastNode - firstNode + offset) << 1; } } /** * Allocates Canonical Huffman code lengths in place based on a sorted frequency array * @param array On input, a sorted array of symbol frequencies; On output, an array of Canonical * Huffman code lengths * @param maximumLength The maximum code length. Must be at least {@code ceil(log2(array.length))} */ public static void allocateHuffmanCodeLengths (final int[] array, final int maximumLength) { switch (array.length) { case 2: array[1] = 1; 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 { int insertDepth = maximumLength - (32 - Integer.numberOfLeadingZeros (nodesToRelocate - 1)); allocateNodeLengthsWithRelocation (array, nodesToRelocate, insertDepth); } } /** * Non-instantiable */ private HuffmanAllocator() { } } libbzip2-java-0.9.1/src/org/itadaki/bzip2/BZip2BlockCompressor.java 0000644 0001750 0001750 00000017227 11745741314 024746 0 ustar osallou osallou /* * Copyright (c) 2011 Matthew Francis * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.itadaki.bzip2; import java.io.IOException; /* * 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) */ /** * Compresses and writes a single BZip2 block */ public class BZip2BlockCompressor { /** * The stream to which compressed BZip2 data is written */ private final BitOutputStream bitOutputStream; /** * CRC builder for the block */ private final CRC32 crc = new CRC32(); /** * The RLE'd block data */ private final byte[] block; /** * Current length of the RLE'd block data */ private int blockLength = 0; /** * A limit beyond which new data will not be accepted into the block */ private final int blockLengthLimit; /** * For each index, {@code true} if that value is present in the block data, otherwise * {@code false} */ private final boolean[] blockValuesPresent = new boolean[256]; /** * The Burrows Wheeler Transformed block data */ private final int[] bwtBlock; /** * The current RLE value being accumulated (undefined when {@link #rleLength} is 0) */ private int rleCurrentValue = -1; /** * The repeat count of the current RLE value */ private int rleLength = 0; /** * Writes an RLE run to the block array, updating the block CRC and present values array as required * @param value The value to write * @param runLength The run length of the value to write */ private void writeRun (final int value, int runLength) { final int blockLength = this.blockLength; final byte[] block = this.block; this.blockValuesPresent[value] = true; this.crc.updateCRC (value, runLength); final byte byteValue = (byte)value; switch (runLength) { case 1: block[blockLength] = byteValue; this.blockLength = blockLength + 1; break; case 2: block[blockLength] = byteValue; block[blockLength + 1] = byteValue; this.blockLength = blockLength + 2; break; case 3: block[blockLength] = byteValue; block[blockLength + 1] = byteValue; block[blockLength + 2] = byteValue; this.blockLength = blockLength + 3; break; default: runLength -= 4; this.blockValuesPresent[runLength] = true; block[blockLength] = byteValue; block[blockLength + 1] = byteValue; block[blockLength + 2] = byteValue; block[blockLength + 3] = byteValue; block[blockLength + 4] = (byte)runLength; this.blockLength = blockLength + 5; break; } } /** * Writes a byte to the block, accumulating to an RLE run where possible * @param value The byte to write * @return {@code true} if the byte was written, or {@code false} if the block is already full */ public boolean write (final int value) { if (this.blockLength > this.blockLengthLimit) { return false; } final int rleCurrentValue = this.rleCurrentValue; final int rleLength = this.rleLength; if (rleLength == 0) { this.rleCurrentValue = value; this.rleLength = 1; } else if (rleCurrentValue != value) { // This path commits us to write 6 bytes - one RLE run (5 bytes) plus one extra writeRun (rleCurrentValue & 0xff, rleLength); this.rleCurrentValue = value; this.rleLength = 1; } else { if (rleLength == 254) { writeRun (rleCurrentValue & 0xff, 255); this.rleLength = 0; } else { this.rleLength = rleLength + 1; } } return true; } /** * Writes an array to the block * @param data The array to write * @param offset The offset within the input data to write from * @param length The number of bytes of input data to write * @return 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 (final byte[] data, int offset, int length) { int written = 0; while (length-- > 0) { if (!write (data[offset++])) { break; } written++; } return written; } /** * Compresses and writes out the block * @throws IOException on any I/O error writing the data */ public void close() throws IOException { // If an RLE run is in progress, write it out if (this.rleLength > 0) { 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 BZip2DivSufSort divSufSort = new BZip2DivSufSort (this.block, this.bwtBlock, this.blockLength); int 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.getCRC()); this.bitOutputStream.writeBoolean (false); // Randomised block flag. We never create randomised blocks this.bitOutputStream.writeBits (24, bwtStartPointer); // Perform the Huffman Encoding stage and write out the encoded data BZip2HuffmanStageEncoder huffmanEncoder = new BZip2HuffmanStageEncoder (this.bitOutputStream, this.blockValuesPresent, this.bwtBlock, this.blockLength); huffmanEncoder.encode(); } /** * Determines if any bytes have been written to the block * @return {@code true} if one or more bytes has been written to the block, otherwise * {@code false} */ public boolean isEmpty() { return ((this.blockLength == 0) && (this.rleLength == 0)); } /** * Gets the CRC of the completed block. Only valid after calling {@link #close()} * @return The block's CRC */ public int getCRC() { return this.crc.getCRC(); } /** * @param bitOutputStream The stream to which compressed BZip2 data is written * @param blockSize 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 (final BitOutputStream bitOutputStream, final 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 {@link #write(int)} } } libbzip2-java-0.9.1/src/org/itadaki/bzip2/BZip2BlockDecompressor.java 0000644 0001750 0001750 00000041453 11745741314 025255 0 ustar osallou osallou /* * Copyright (c) 2011 Matthew Francis * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.itadaki.bzip2; import java.io.IOException; /* * 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()) */ /** * Reads and decompresses a single BZip2 block */ public 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 final 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 }; /** * Provides bits of input to decode */ private final BitInputStream bitInputStream; /** * Calculates the block CRC from the fully decoded bytes of the block */ private final CRC32 crc = new CRC32(); /** * The CRC of the current block as read from the block header */ private final int blockCRC; /** * {@code true} if the current block is randomised, otherwise {@code false} */ private final boolean 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 final byte[] huffmanSymbolMap = new byte[256]; /* Move To Front stage */ /** * Counts of each byte value within the {@link bwtTransformedArray} data. Collected at the Move * To Front stage, consumed by the Inverse Burrows Wheeler Transform stage */ private final 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; /** * 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 = 0; /** * If the current block is randomised, the remaining count at the current RNUMS position */ private int randomCount = RNUMS[0] - 1; /** * Read and decode the block's Huffman tables * @return A decoder for the Huffman stage that uses the decoded tables * @throws IOException if the input stream reaches EOF before all table data has been read */ private BZip2HuffmanStageDecoder readHuffmanTables() throws IOException { final BitInputStream bitInputStream = this.bitInputStream; final byte[] huffmanSymbolMap = this.huffmanSymbolMap; final byte[][] tableCodeLengths = new byte[BZip2Constants.HUFFMAN_MAXIMUM_TABLES][BZip2Constants.HUFFMAN_MAXIMUM_ALPHABET_SIZE]; /* Read Huffman symbol to output byte map */ int huffmanUsedRanges = bitInputStream.readBits (16); int huffmanSymbolCount = 0; for (int i = 0; i < 16; i++) { if ((huffmanUsedRanges & ((1 << 15) >>> i)) != 0) { for (int j = 0, k = i << 4; j < 16; j++, k++) { if (bitInputStream.readBoolean()) { huffmanSymbolMap[huffmanSymbolCount++] = (byte)k; } } } } int endOfBlockSymbol = huffmanSymbolCount + 1; this.huffmanEndOfBlockSymbol = endOfBlockSymbol; /* Read total number of tables and selectors*/ final int totalTables = bitInputStream.readBits (3); final int totalSelectors = bitInputStream.readBits (15); /* Read and decode MTFed Huffman selector list */ final MoveToFront tableMTF = new MoveToFront(); final byte[] selectors = new byte[totalSelectors]; for (int selector = 0; selector < totalSelectors; selector++) { selectors[selector] = tableMTF.indexToFront (bitInputStream.readUnary()); } /* Read the Canonical Huffman code lengths for each table */ for (int table = 0; table < totalTables; table++) { int currentLength = bitInputStream.readBits (5); for (int i = 0; i <= endOfBlockSymbol; i++) { while (bitInputStream.readBoolean()) { currentLength += bitInputStream.readBoolean() ? -1 : 1; } tableCodeLengths[table][i] = (byte)currentLength; } } return new BZip2HuffmanStageDecoder (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 * @param huffmanDecoder The Huffman decoder through which symbols are read * @throws IOException if an end-of-block symbol was not decoded within the declared block size */ private void decodeHuffmanData (final BZip2HuffmanStageDecoder huffmanDecoder) throws IOException { final byte[] bwtBlock = this.bwtBlock; final byte[] huffmanSymbolMap = this.huffmanSymbolMap; final int streamBlockSize = this.bwtBlock.length; final int huffmanEndOfBlockSymbol = this.huffmanEndOfBlockSymbol; final int[] bwtByteCounts = this.bwtByteCounts; final MoveToFront symbolMTF = new MoveToFront(); int bwtBlockLength = 0; int repeatCount = 0; int repeatIncrement = 1; int mtfValue = 0; for (;;) { final int nextSymbol = huffmanDecoder.nextSymbol(); if (nextSymbol == BZip2Constants.HUFFMAN_SYMBOL_RUNA) { repeatCount += repeatIncrement; repeatIncrement <<= 1; } else if (nextSymbol == BZip2Constants.HUFFMAN_SYMBOL_RUNB) { repeatCount += repeatIncrement << 1; repeatIncrement <<= 1; } else { if (repeatCount > 0) { if (bwtBlockLength + repeatCount > streamBlockSize) { throw new IOException ("BZip2 block exceeds declared block size"); } final byte nextByte = huffmanSymbolMap[mtfValue]; bwtByteCounts[nextByte & 0xff] += repeatCount; while (--repeatCount >= 0) { bwtBlock[bwtBlockLength++] = nextByte; } repeatCount = 0; repeatIncrement = 1; } if (nextSymbol == huffmanEndOfBlockSymbol) break; if (bwtBlockLength >= streamBlockSize) { throw new IOException ("BZip2 block exceeds declared block size"); } mtfValue = symbolMTF.indexToFront (nextSymbol - 1) & 0xff; final byte nextByte = huffmanSymbolMap[mtfValue]; bwtByteCounts[nextByte & 0xff]++; bwtBlock[bwtBlockLength++] = nextByte; } } this.bwtBlockLength = bwtBlockLength; } /** * Set up the Inverse Burrows-Wheeler Transform merged pointer array * @param bwtStartPointer The start pointer into the BWT array * @throws IOException if the given start pointer is invalid */ private void initialiseInverseBWT (final int bwtStartPointer) throws IOException { final byte[] bwtBlock = this.bwtBlock; final int[] bwtMergedPointers = new int[this.bwtBlockLength]; final int[] characterBase = new int[256]; if ((bwtStartPointer < 0) || (bwtStartPointer >= this.bwtBlockLength)) { throw new IOException ("BZip2 start pointer invalid"); } // Cumulatise character counts System.arraycopy (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 = 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 * @return The decoded byte */ private int decodeNextBWTByte() { int mergedPointer = this.bwtCurrentMergedPointer; int nextDecodedByte = mergedPointer & 0xff; this.bwtCurrentMergedPointer = this.bwtMergedPointers[mergedPointer >>> 8]; if (this.blockRandomised) { if (--this.randomCount == 0) { nextDecodedByte ^= 1; this.randomIndex = (this.randomIndex + 1) % 512; this.randomCount = RNUMS[this.randomIndex]; } } this.bwtBytesDecoded++; return nextDecodedByte; } /** * Decodes a byte from the final Run-Length Encoding stage, pulling a new byte from the * Burrows-Wheeler Transform stage when required * @return 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 = 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 = 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 * @param destination The array to write to * @param offset The starting position within the array * @param length The number of bytes to read * @return The number of bytes actually read, or -1 if there are no bytes left in the block */ public int read (final byte[] destination, int offset, final int length) { int i; for (i = 0; i < length; i++, offset++) { int decoded = read(); if (decoded == -1) { return (i == 0) ? -1 : i; } destination[offset] = (byte)decoded; } return i; } /** * Verify and return the block CRC. This method may only be called after all of the block's * bytes have been read * @return The block CRC * @throws IOException if the CRC verification failed */ public int checkCRC() throws IOException { if (this.blockCRC != this.crc.getCRC()) { throw new IOException ("BZip2 block CRC error"); } return this.crc.getCRC(); } /** * @param bitInputStream The BitInputStream to read from * @param blockSize The maximum decoded size of the block * @throws IOException If the block could not be decoded */ public BZip2BlockDecompressor (final BitInputStream bitInputStream, final int blockSize) throws IOException { this.bitInputStream = bitInputStream; this.bwtBlock = new byte[blockSize]; // Read block header this.blockCRC = this.bitInputStream.readInteger(); this.blockRandomised = this.bitInputStream.readBoolean(); int bwtStartPointer = this.bitInputStream.readBits (24); // Read block data and decode through to the Inverse Burrows Wheeler Transform stage BZip2HuffmanStageDecoder huffmanDecoder = readHuffmanTables(); decodeHuffmanData (huffmanDecoder); initialiseInverseBWT (bwtStartPointer); } } libbzip2-java-0.9.1/src/org/itadaki/bzip2/MoveToFront.java 0000644 0001750 0001750 00000010134 11745741314 023200 0 ustar osallou osallou /* * Copyright (c) 2011 Matthew Francis * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.itadaki.bzip2; /** * A 256 entry Move To Front transform */ public class MoveToFront { /** * The Move To Front list */ private final byte[] mtf = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127, (byte)128, (byte)129, (byte)130, (byte)131, (byte)132, (byte)133, (byte)134, (byte)135, (byte)136, (byte)137, (byte)138, (byte)139, (byte)140, (byte)141, (byte)142, (byte)143, (byte)144, (byte)145, (byte)146, (byte)147, (byte)148, (byte)149, (byte)150, (byte)151, (byte)152, (byte)153, (byte)154, (byte)155, (byte)156, (byte)157, (byte)158, (byte)159, (byte)160, (byte)161, (byte)162, (byte)163, (byte)164, (byte)165, (byte)166, (byte)167, (byte)168, (byte)169, (byte)170, (byte)171, (byte)172, (byte)173, (byte)174, (byte)175, (byte)176, (byte)177, (byte)178, (byte)179, (byte)180, (byte)181, (byte)182, (byte)183, (byte)184, (byte)185, (byte)186, (byte)187, (byte)188, (byte)189, (byte)190, (byte)191, (byte)192, (byte)193, (byte)194, (byte)195, (byte)196, (byte)197, (byte)198, (byte)199, (byte)200, (byte)201, (byte)202, (byte)203, (byte)204, (byte)205, (byte)206, (byte)207, (byte)208, (byte)209, (byte)210, (byte)211, (byte)212, (byte)213, (byte)214, (byte)215, (byte)216, (byte)217, (byte)218, (byte)219, (byte)220, (byte)221, (byte)222, (byte)223, (byte)224, (byte)225, (byte)226, (byte)227, (byte)228, (byte)229, (byte)230, (byte)231, (byte)232, (byte)233, (byte)234, (byte)235, (byte)236, (byte)237, (byte)238, (byte)239, (byte)240, (byte)241, (byte)242, (byte)243, (byte)244, (byte)245, (byte)246, (byte)247, (byte)248, (byte)249, (byte)250, (byte)251, (byte)252, (byte)253, (byte)254, (byte)255 }; /** * Moves a value to the head of the MTF list (forward Move To Front transform) * @param value The value to move * @return The position the value moved from */ public int valueToFront (final byte value) { final byte[] mtf = this.mtf; int index = 0; byte temp = mtf[0]; if (value != temp) { mtf[0] = value; while (value != temp) { index++; final byte temp2 = temp; temp = mtf[index]; 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) * @param index The index to move * @return The value at the given index */ public byte indexToFront (final int index) { final byte[] mtf = this.mtf; final byte value = mtf[index]; System.arraycopy (mtf, 0, mtf, 1, index); mtf[0] = value; return value; } } libbzip2-java-0.9.1/src/org/itadaki/bzip2/BZip2Constants.java 0000644 0001750 0001750 00000005502 11745741314 023604 0 ustar osallou osallou /* * Copyright (c) 2011 Matthew Francis * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.itadaki.bzip2; /** * BZip2 constants shared between the compressor and decompressor */ interface BZip2Constants { /** * First three bytes of the block header marker */ static final int BLOCK_HEADER_MARKER_1 = 0x314159; /** * Last three bytes of the block header marker */ static final int BLOCK_HEADER_MARKER_2 = 0x265359; /** * Number of symbols decoded after which a new Huffman table is selected */ static final int HUFFMAN_GROUP_RUN_LENGTH = 50; /** * Maximum possible Huffman alphabet size */ static final int HUFFMAN_MAXIMUM_ALPHABET_SIZE = 258; /** * The longest Huffman code length created by the encoder */ static final int HUFFMAN_ENCODE_MAXIMUM_CODE_LENGTH = 20; /** * The longest Huffman code length accepted by the decoder */ static final int HUFFMAN_DECODE_MAXIMUM_CODE_LENGTH = 23; /** * Maximum number of alternative Huffman tables */ static final int HUFFMAN_MAXIMUM_TABLES = 6; /** * Maximum possible number of Huffman table selectors */ static final int HUFFMAN_MAXIMUM_SELECTORS = ((900000 / HUFFMAN_GROUP_RUN_LENGTH) + 2); /** * Huffman symbol used for run-length encoding */ static final int HUFFMAN_SYMBOL_RUNA = 0; /** * Huffman symbol used for run-length encoding */ static final int HUFFMAN_SYMBOL_RUNB = 1; /** * First three bytes of the end of stream marker */ static final int STREAM_END_MARKER_1 = 0x177245; /** * Last three bytes of the end of stream marker */ static final int STREAM_END_MARKER_2 = 0x385090; /** * 'B' 'Z' that marks the start of a BZip2 stream */ static final int STREAM_START_MARKER_1 = 0x425a; /** * 'h' that distinguishes BZip from BZip2 */ static final int STREAM_START_MARKER_2 = 0x68; } libbzip2-java-0.9.1/src/org/itadaki/bzip2/BZip2HuffmanStageEncoder.java 0000644 0001750 0001750 00000036223 11745741314 025504 0 ustar osallou osallou /* * Copyright (c) 2011 Matthew Francis * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.itadaki.bzip2; import java.io.IOException; import java.util.Arrays; /** * An encoder for the BZip2 Huffman encoding stage */ class BZip2HuffmanStageEncoder { /** * Used in initial Huffman table generation */ private static final int HUFFMAN_HIGH_SYMBOL_COST = 15; /** * The BitOutputStream from which Huffman codes are written */ private final BitOutputStream bitOutputStream; /** * The Burrows-Wheeler transformed block */ private final int[] bwtBlock; /** * Actual length of the BWT data */ private int bwtLength; /** * At each position, {@code true} if the byte value with that index is present within the block, * otherwise {@code false} */ private final boolean[] bwtValuesInUse; /** * The output of the Move To Front stage */ private final char[] mtfBlock; /** * The actual number of values contained in the {@link mtf} array */ private int mtfLength; /** * The Huffman alphabet size */ private int huffmanAlphabetSize; /** * The global frequencies of values within the {@link mtf} array */ private final int[] mtfSymbolFrequencies = new int[BZip2Constants.HUFFMAN_MAXIMUM_ALPHABET_SIZE]; /** * The lengths of the Canonical Huffman codes for each table */ private final int[][] huffmanCodeLengths = new int[BZip2Constants.HUFFMAN_MAXIMUM_TABLES][BZip2Constants.HUFFMAN_MAXIMUM_ALPHABET_SIZE]; /** * A table of Canonical Huffman codes for each table. The value at each position is ((code length << 24) | code) */ private final int[][] huffmanMergedCodeSymbols = new int[BZip2Constants.HUFFMAN_MAXIMUM_TABLES][BZip2Constants.HUFFMAN_MAXIMUM_ALPHABET_SIZE]; /** * The selectors for each segment */ private final byte[] selectors = new byte[BZip2Constants.HUFFMAN_MAXIMUM_SELECTORS]; /** * Selects an appropriate table count for a given MTF length * @param mtfLength The length to select a table count for * @return The selected table count */ private static int selectTableCount (final int mtfLength) { if (mtfLength >= 2400) return 6; if (mtfLength >= 1200) return 5; if (mtfLength >= 600) return 4; if (mtfLength >= 200) return 3; return 2; } /** * Performs the Move To Front transform and Run Length Encoding[1] stages */ private void moveToFrontAndRunLengthEncode() { final int bwtLength = this.bwtLength; final boolean[] bwtValuesInUse = this.bwtValuesInUse; final int[] bwtBlock = this.bwtBlock; final char[] mtfBlock = this.mtfBlock; final int[] mtfSymbolFrequencies = this.mtfSymbolFrequencies; final byte[] huffmanSymbolMap = new byte[256]; final MoveToFront symbolMTF = new MoveToFront(); int totalUniqueValues = 0; for (int i = 0; i < 256; i++) { if (bwtValuesInUse[i]) { huffmanSymbolMap[i] = (byte) totalUniqueValues++; } } final int endOfBlockSymbol = totalUniqueValues + 1; int mtfIndex = 0; int repeatCount = 0; int totalRunAs = 0; int totalRunBs = 0; for (int i = 0; i < bwtLength; i++) { // Move To Front int mtfPosition = symbolMTF.valueToFront (huffmanSymbolMap[bwtBlock[i] & 0xff]); // Run Length Encode if (mtfPosition == 0) { repeatCount++; } else { if (repeatCount > 0) { repeatCount--; while (true) { if ((repeatCount & 1) == 0) { mtfBlock[mtfIndex++] = BZip2Constants.HUFFMAN_SYMBOL_RUNA; totalRunAs++; } else { mtfBlock[mtfIndex++] = BZip2Constants.HUFFMAN_SYMBOL_RUNB; totalRunBs++; } if (repeatCount < 2) { break; } repeatCount = (repeatCount - 2) >>> 1; } repeatCount = 0; } mtfBlock[mtfIndex++] = (char) (mtfPosition + 1); mtfSymbolFrequencies[mtfPosition + 1]++; } } if (repeatCount > 0) { repeatCount--; while (true) { if ((repeatCount & 1) == 0) { mtfBlock[mtfIndex++] = BZip2Constants.HUFFMAN_SYMBOL_RUNA; totalRunAs++; } else { mtfBlock[mtfIndex++] = BZip2Constants.HUFFMAN_SYMBOL_RUNB; totalRunBs++; } if (repeatCount < 2) { break; } repeatCount = (repeatCount - 2) >>> 1; } } mtfBlock[mtfIndex] = (char) endOfBlockSymbol; mtfSymbolFrequencies[endOfBlockSymbol]++; mtfSymbolFrequencies[BZip2Constants.HUFFMAN_SYMBOL_RUNA] += totalRunAs; mtfSymbolFrequencies[BZip2Constants.HUFFMAN_SYMBOL_RUNB] += totalRunBs; this.mtfLength = mtfIndex + 1; this.huffmanAlphabetSize = totalUniqueValues + 2; } /** * 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 {@link #optimiseSelectorsAndHuffmanTables(int)}. * @param totalTables The total number of Huffman tables */ private void generateInitialHuffmanCodeLengths (final int totalTables) { final int[][] huffmanCodeLengths = this.huffmanCodeLengths; final int[] mtfSymbolFrequencies = this.mtfSymbolFrequencies; final int huffmanAlphabetSize = this.huffmanAlphabetSize; int remainingLength = this.mtfLength; int lowCostEnd = -1; for (int i = 0; i < totalTables; i++) { final int targetCumulativeFrequency = remainingLength / (totalTables - i); final int lowCostStart = lowCostEnd + 1; int actualCumulativeFrequency = 0; while ((actualCumulativeFrequency < targetCumulativeFrequency) && (lowCostEnd < (huffmanAlphabetSize - 1))) { actualCumulativeFrequency += mtfSymbolFrequencies[++lowCostEnd]; } if ((lowCostEnd > lowCostStart) && (i != 0) && (i != (totalTables - 1)) && (((totalTables - i) & 1) == 0)) { actualCumulativeFrequency -= mtfSymbolFrequencies[lowCostEnd--]; } final int[] tableCodeLengths = huffmanCodeLengths[i]; for (int j = 0; j < huffmanAlphabetSize; j++) { if ((j < lowCostStart) || (j > lowCostEnd)) { tableCodeLengths[j] = HUFFMAN_HIGH_SYMBOL_COST; } } remainingLength -= actualCumulativeFrequency; } } /** * Iteratively co-optimise the selector list and the alternative Huffman table code lengths * @param totalTables The total number of tables * @return The total number of selectors */ private int optimiseSelectorsAndHuffmanTables (final int totalTables) { final int MAXIMUM_ITERATIONS = 4; final char[] mtf = this.mtfBlock; final byte[] selectors = this.selectors; final int[][] huffmanCodeLengths = this.huffmanCodeLengths; final int mtfLength = this.mtfLength; final int huffmanAlphabetSize = this.huffmanAlphabetSize; final int[] sortedFrequencyMap = new int[huffmanAlphabetSize]; final int[] sortedFrequencies = new int[huffmanAlphabetSize]; int selectorIndex = 0; for (int iteration = MAXIMUM_ITERATIONS - 1; iteration >= 0; iteration--) { int[][] tableFrequencies = new int[BZip2Constants.HUFFMAN_MAXIMUM_TABLES][huffmanAlphabetSize]; selectorIndex = 0; // Find the best table for each group based on the current Huffman code lengths for (int groupStart = 0; groupStart < mtfLength;) { final int groupEnd = Math.min (groupStart + BZip2Constants.HUFFMAN_GROUP_RUN_LENGTH - 1, mtfLength - 1); short[] cost = new short[BZip2Constants.HUFFMAN_MAXIMUM_TABLES]; for (int i = groupStart; i <= groupEnd; i++) { final int value = mtf[i]; for (int j = 0; j < totalTables; j++) { cost[j] += huffmanCodeLengths[j][value]; } } int bestTable = 0; int bestCost = cost[0]; for (int i = 1 ; i < totalTables; i++) { final int tableCost = cost[i]; if (tableCost < bestCost) { bestCost = tableCost; bestTable = i; } } final int[] bestGroupFrequencies = tableFrequencies[bestTable]; for (int i = groupStart; i <= groupEnd; i++) { bestGroupFrequencies[mtf[i]]++; } if (iteration == 0) { selectors[selectorIndex++] = (byte) bestTable; } groupStart = groupEnd + 1; } // Calculate new Huffman code lengths based on the table frequencies accumulated in this iteration for (int i = 0; i < totalTables; i++) { for (int j = 0; j < huffmanAlphabetSize; j++) { sortedFrequencyMap[j] = (tableFrequencies[i][j] << 9) | j; } Arrays.sort (sortedFrequencyMap); for (int j = 0; j < huffmanAlphabetSize; j++) { sortedFrequencies[j] = sortedFrequencyMap[j] >>> 9; } HuffmanAllocator.allocateHuffmanCodeLengths (sortedFrequencies, BZip2Constants.HUFFMAN_ENCODE_MAXIMUM_CODE_LENGTH); for (int j = 0; j < huffmanAlphabetSize; j++) { huffmanCodeLengths[i][sortedFrequencyMap[j] & 0x1ff] = sortedFrequencies[j]; } } } return selectorIndex; } /** * Assigns Canonical Huffman codes based on the calculated lengths * @param totalTables The total number of Huffman tables */ private void assignHuffmanCodeSymbols (final int totalTables) { final int[][] huffmanMergedCodeSymbols = this.huffmanMergedCodeSymbols; final int[][] huffmanCodeLengths = this.huffmanCodeLengths; final int huffmanAlphabetSize = this.huffmanAlphabetSize; for (int i = 0; i < totalTables; i++) { final int[] tableLengths = huffmanCodeLengths[i]; int minimumLength = 32; int maximumLength = 0; for (int j = 0; j < huffmanAlphabetSize; j++) { final int length = tableLengths[j]; if (length > maximumLength) { maximumLength = length; } if (length < minimumLength) { minimumLength = length; } } int code = 0; for (int j = minimumLength; j <= maximumLength; j++) { for (int k = 0; k < huffmanAlphabetSize; k++) { if ((huffmanCodeLengths[i][k] & 0xff) == j) { huffmanMergedCodeSymbols[i][k] = (j << 24) | code; code++; } } code <<= 1; } } } /** * Write the Huffman symbol to output byte map * @throws IOException on any I/O error writing the data */ private void writeSymbolMap() throws IOException { BitOutputStream bitOutputStream = this.bitOutputStream; final boolean[] bwtValuesInUse = this.bwtValuesInUse; final boolean[] condensedInUse = new boolean[16]; for (int i = 0; i < 16; i++) { for (int j = 0, k = i << 4; j < 16; j++, k++) { if (bwtValuesInUse[k]) { condensedInUse[i] = true; } } } for (int i = 0; i < 16; i++) { bitOutputStream.writeBoolean (condensedInUse[i]); } for (int i = 0; i < 16; i++) { if (condensedInUse[i]) { for (int j = 0, k = i * 16; j < 16; j++, k++) { bitOutputStream.writeBoolean (bwtValuesInUse[k]); } } } } /** * Write total number of Huffman tables and selectors, and the MTFed Huffman selector list * @param totaTables The total number of Huffman tables * @param totalSelectors The total number of selectors * @throws IOException on any I/O error writing the data */ private void writeSelectors (final int totaTables, final int totalSelectors) throws IOException { final BitOutputStream bitOutputStream = this.bitOutputStream; final byte[] selectors = this.selectors; bitOutputStream.writeBits (3, totaTables); bitOutputStream.writeBits (15, totalSelectors); MoveToFront selectorMTF = new MoveToFront(); for (int i = 0; i < totalSelectors; i++) { bitOutputStream.writeUnary (selectorMTF.valueToFront (selectors[i])); } } /** * Write the Canonical Huffman code lengths for each table * @param totalTables The total number of tables * @throws IOException on any I/O error writing the data */ private void writeHuffmanCodeLengths (final int totalTables) throws IOException { final BitOutputStream bitOutputStream = this.bitOutputStream; final int[][] huffmanCodeLengths = this.huffmanCodeLengths; final int huffmanAlphabetSize = this.huffmanAlphabetSize; for (int i = 0; i < totalTables; i++) { final int[] tableLengths = huffmanCodeLengths[i]; int currentLength = tableLengths[0]; bitOutputStream.writeBits (5, currentLength); for (int j = 0; j < huffmanAlphabetSize; j++) { final int codeLength = tableLengths[j]; final int value = (currentLength < codeLength) ? 2 : 3; int delta = Math.abs (codeLength - currentLength); while (delta-- > 0) { bitOutputStream.writeBits (2, value); } bitOutputStream.writeBoolean (false); currentLength = codeLength; } } } /** * Writes out the encoded block data * @throws IOException on any I/O error writing the data */ private void writeBlockData() throws IOException { final BitOutputStream bitOutputStream = this.bitOutputStream; final int[][] huffmanMergedCodeSymbols = this.huffmanMergedCodeSymbols; final byte[] selectors = this.selectors; final char[] mtf = this.mtfBlock; final int mtfLength = this.mtfLength; int selectorIndex = 0; for (int mtfIndex = 0; mtfIndex < mtfLength;) { final int groupEnd = Math.min (mtfIndex + BZip2Constants.HUFFMAN_GROUP_RUN_LENGTH - 1, mtfLength - 1); final int[] tableMergedCodeSymbols = huffmanMergedCodeSymbols[selectors[selectorIndex++]]; while (mtfIndex <= groupEnd) { final int mergedCodeSymbol = tableMergedCodeSymbols[mtf[mtfIndex++]]; bitOutputStream.writeBits (mergedCodeSymbol >>> 24, mergedCodeSymbol); } } } /** * Encodes and writes the block data * @throws IOException on any I/O error writing the data */ public void encode() throws IOException { moveToFrontAndRunLengthEncode(); final int totalTables = selectTableCount (this.mtfLength); generateInitialHuffmanCodeLengths (totalTables); final int totalSelectors = optimiseSelectorsAndHuffmanTables (totalTables); assignHuffmanCodeSymbols (totalTables); writeSymbolMap(); writeSelectors (totalTables, totalSelectors); writeHuffmanCodeLengths (totalTables); writeBlockData(); } /** * @param bitOutputStream The BitOutputStream to write to * @param bwtValuesInUse The byte values that are actually present within the block * @param bwtBlock The Burrows Wheeler Transformed data * @param bwtLength The actual length of the BWT data */ public BZip2HuffmanStageEncoder (final BitOutputStream bitOutputStream, final boolean[] bwtValuesInUse, final int[] bwtBlock, final int bwtLength) { this.bitOutputStream = bitOutputStream; this.mtfBlock = new char[2 * bwtBlock.length]; this.bwtValuesInUse = bwtValuesInUse; this.bwtBlock = bwtBlock; this.bwtLength = bwtLength; } } libbzip2-java-0.9.1/src/org/itadaki/bzip2/BitOutputStream.java 0000644 0001750 0001750 00000007723 11745741314 024103 0 ustar osallou osallou /* * Copyright (c) 2011 Matthew Francis * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.itadaki.bzip2; import java.io.IOException; import java.io.OutputStream; /** *An OutputStream wrapper that 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 */ public class BitOutputStream { /** * The stream to which bits are written */ private final OutputStream outputStream; /** * A buffer of bits waiting to be written to the output stream */ private int bitBuffer; /** * The number of bits currently buffered in {@link #bitBuffer} */ private int bitCount; /** * Writes a single bit to the wrapped output stream * @param value The bit to write * @throws IOException if an error occurs writing to the stream */ public void writeBoolean (final boolean value) throws IOException { int bitCount = this.bitCount + 1; int bitBuffer = this.bitBuffer | ((value ? 1 : 0) << (32 - bitCount)); if (bitCount == 8) { this.outputStream.write (bitBuffer >>> 24); bitBuffer = 0; bitCount = 0; } this.bitBuffer = bitBuffer; this.bitCount = bitCount; } /** * Writes a zero-terminated unary number to the wrapped output stream * @param value The number to write (must be non-negative) * @throws IOException if an error occurs writing to the stream */ public void writeUnary (int value) throws IOException { while (value-- > 0) { writeBoolean (true); } writeBoolean (false); } /** * Writes up to 24 bits to the wrapped output stream * @param count The number of bits to write (maximum 24) * @param value The bits to write * @throws IOException if an error occurs writing to the stream */ public void writeBits (final int count, final int value) throws IOException { int bitCount = this.bitCount; int bitBuffer = this.bitBuffer | ((value << (32 - count)) >>> bitCount); bitCount += count; while (bitCount >= 8) { this.outputStream.write (bitBuffer >>> 24); bitBuffer <<= 8; bitCount -= 8; } this.bitBuffer = bitBuffer; this.bitCount = bitCount; } /** * Writes an integer as 32 bits of output * @param value The integer to write * @throws IOException if an error occurs writing to the stream */ public void writeInteger (final int value) throws IOException { writeBits (16, (value >>> 16) & 0xffff); writeBits (16, value & 0xffff); } /** * Writes any remaining bits to the output stream, zero padding to a whole byte as required * @throws IOException if an error occurs writing to the stream */ public void flush() throws IOException { if (this.bitCount > 0) { writeBits (8 - this.bitCount, 0); } } /** * @param outputStream The OutputStream to wrap */ public BitOutputStream (final OutputStream outputStream) { this.outputStream = outputStream; } } libbzip2-java-0.9.1/src/org/itadaki/bzip2/BitInputStream.java 0000644 0001750 0001750 00000010611 11745741314 023670 0 ustar osallou osallou /* * Copyright (c) 2011 Matthew Francis * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.itadaki.bzip2; import java.io.IOException; import java.io.InputStream; /** *
An InputStream wrapper that allows the reading 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 read from the wrapped stream when more bits are required
*/ public class BitInputStream { /** * The stream from which bits are read */ private final InputStream inputStream; /** * A buffer of bits read from the input stream that have not yet been returned */ private int bitBuffer; /** * The number of bits currently buffered in {@link #bitBuffer} */ private int bitCount; /** * Reads a single bit from the wrapped input stream * @return {@code true} if the bit read was {@code 1}, otherwise {@code false} * @throws IOException if no more bits are available in the input stream */ public boolean readBoolean() throws IOException { int bitBuffer = this.bitBuffer; int bitCount = this.bitCount; if (bitCount > 0) { bitCount--; } else { int byteRead = this.inputStream.read(); if (byteRead < 0) { throw new IOException ("Unexpected end of stream"); } bitBuffer = (bitBuffer << 8) | byteRead; bitCount += 7; this.bitBuffer = bitBuffer; } this.bitCount = bitCount; return ((bitBuffer & (1 << bitCount))) != 0; } /** * Reads a zero-terminated unary number from the wrapped input stream * @return The unary number * @throws IOException if no more bits are available in the input stream */ public int readUnary() throws IOException { int bitBuffer = this.bitBuffer; int bitCount = this.bitCount; int unaryCount = 0; for (;;) { if (bitCount > 0) { bitCount--; } else { int byteRead = this.inputStream.read(); if (byteRead < 0) { throw new IOException ("Unexpected end of stream"); } bitBuffer = (bitBuffer << 8) | byteRead; bitCount += 7; } if (((bitBuffer & (1 << bitCount))) == 0) { this.bitBuffer = bitBuffer; this.bitCount = bitCount; return unaryCount; } unaryCount++; } } /** * Reads up to 24 bits from the wrapped input stream * @param count The number of bits to read (maximum 24) * @return The bits requested, right-aligned within the integer * @throws IOException if more bits are requested than are available in the input stream */ public int readBits (final int count) throws IOException { int bitBuffer = this.bitBuffer; int bitCount = this.bitCount; if (bitCount < count) { while (bitCount < count) { int byteRead = this.inputStream.read(); if (byteRead < 0) { throw new IOException ("Unexpected end of stream"); } bitBuffer = (bitBuffer << 8) | byteRead; bitCount += 8; } this.bitBuffer = bitBuffer; } bitCount -= count; this.bitCount = bitCount; return (bitBuffer >>> bitCount) & ((1 << count) - 1); } /** * Reads 32 bits of input as an integer * @return The integer read * @throws IOException if 32 bits are not available in the input stream */ public int readInteger() throws IOException { return (readBits (16) << 16) | (readBits (16)); } /** * @param inputStream The InputStream to wrap */ public BitInputStream (final InputStream inputStream) { this.inputStream = inputStream; } } libbzip2-java-0.9.1/src/org/itadaki/bzip2/BZip2OutputStream.java 0000644 0001750 0001750 00000014143 11745741314 024305 0 ustar osallou osallou /* * Copyright (c) 2011 Matthew Francis * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package org.itadaki.bzip2; import java.io.IOException; import java.io.OutputStream; /** *An OutputStream wrapper that compresses BZip2 data
* *Instances of this class are not threadsafe.
*/ public class BZip2OutputStream extends OutputStream { /** * The stream to which compressed BZip2 data is written */ private OutputStream outputStream; /** * An OutputStream wrapper that provides bit-level writes */ private BitOutputStream bitOutputStream; /** * (@code true} if the compressed stream has been finished, otherwise {@code false} */ private boolean streamFinished = false; /** * The declared maximum block size of the stream (before final run-length decoding) */ private final int streamBlockSize; /** * The merged CRC of all blocks compressed so far */ private int streamCRC = 0; /** * The compressor for the current block */ private BZip2BlockCompressor blockCompressor; /* (non-Javadoc) * @see java.io.OutputStream#write(int) */ @Override public void write (final int value) throws IOException { if (this.outputStream == null) { throw new IOException ("Stream closed"); } if (this.streamFinished) { throw new IOException ("Write beyond end of stream"); } if (!this.blockCompressor.write (value & 0xff)) { closeBlock(); initialiseNextBlock(); this.blockCompressor.write (value & 0xff); } } /* (non-Javadoc) * @see java.io.OutputStream#write(byte[], int, int) */ @Override public void write (final byte[] data, int offset, int length) throws IOException { if (this.outputStream == null) { throw new IOException ("Stream closed"); } if (this.streamFinished) { throw new IOException ("Write beyond end of stream"); } int bytesWritten; while (length > 0) { if ((bytesWritten = this.blockCompressor.write (data, offset, length)) < length) { closeBlock(); initialiseNextBlock(); } offset += bytesWritten; length -= bytesWritten; } } /* (non-Javadoc) * @see java.io.OutputStream#close() */ @Override public void close() throws IOException { if (this.outputStream != null) { finish(); this.outputStream.close(); this.outputStream = null; } } /** * 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 * @throws IOException on any I/O error writing to the output stream */ private void closeBlock() throws IOException { if (this.blockCompressor.isEmpty()) { return; } this.blockCompressor.close(); int blockCRC = this.blockCompressor.getCRC(); this.streamCRC = ((this.streamCRC << 1) | (this.streamCRC >>> 31)) ^ blockCRC; } /** * Compresses and writes out any as yet unwritten data, then writes the end of the BZip2 stream. * The underlying OutputStream is not closed * @throws IOException on any I/O error writing to the output stream */ public void finish() throws IOException { if (!this.streamFinished) { this.streamFinished = true; try { 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; } } } /** * @param outputStream The output stream to write to * @param blockSizeMultiplier The BZip2 block size as a multiple of 100,000 bytes (minimum 1, * maximum 9). 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
* @throws IOException on any I/O error writing to the output stream
*/
public BZip2OutputStream (final OutputStream outputStream, final int blockSizeMultiplier) throws IOException {
if (outputStream == null) {
throw new IllegalArgumentException ("Null output stream");
}
if ((blockSizeMultiplier < 1) || (blockSizeMultiplier > 9)) {
throw new IllegalArgumentException ("Invalid BZip2 block size" + blockSizeMultiplier);
}
this.streamBlockSize = blockSizeMultiplier * 100000;
this.outputStream = outputStream;
this.bitOutputStream = new BitOutputStream (this.outputStream);
this.bitOutputStream.writeBits (16, BZip2Constants.STREAM_START_MARKER_1);
this.bitOutputStream.writeBits (8, BZip2Constants.STREAM_START_MARKER_2);
this.bitOutputStream.writeBits (8, '0' + blockSizeMultiplier);
initialiseNextBlock();
}
/**
* Constructs a BZip2 stream compressor with the maximum (900,000 byte) block size
* @param outputStream The output stream to write to
* @throws IOException on any I/O error writing to the output stream
*/
public BZip2OutputStream (final OutputStream outputStream) throws IOException {
this (outputStream, 9);
}
}
libbzip2-java-0.9.1/src/test/ 0000755 0001750 0001750 00000000000 11745741315 015651 5 ustar osallou osallou libbzip2-java-0.9.1/src/test/TestBZip2DivSufSort.java 0000644 0001750 0001750 00000002776 11745741314 022306 0 ustar osallou osallou package test;
import static org.junit.Assert.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import org.itadaki.bzip2.BZip2DivSufSort;
import org.junit.Test;
/**
* Tests BZip2DivSufSort
*/
public class TestBZip2DivSufSort {
/**
* Tests a bug whereby trIntroSort() doesn't terminate
* @throws IOException
*/
@Test(timeout=2000)
public void testBug1() throws IOException {
String expectedData =
"dddddddddeeeeeeeeesssssssssyyyyyyyyy,,,,,,,,,eeeeeeeeeaaaaaaaaassssssssseeeeeeeeesss" +
"ssssssbbbbbbbbbwwwwwwwww hhhhhhhhhlllllllllMMMMMMMMM wwwwwwwwwmmmmmm" +
"mmmeeeeeeeeeaaaaaaaaatttttttttlllllllllccccccccceeeeeeeeelllllllll " +
"wwwwwwwwwhhhhhhhhh lllllllll tttttttttfffffffff aaaaaaaaasss" +
"ssssssnnnnnnnnnaaaaaaaaatttttttttaaaaaaaaaaaaaaaaaa iiiiiiiiitttttttttiiiiii" +
"iiiiiiiiiiiiooooooooo rrrrrrrrr ";
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
for (int i = 0; i < 9; i++) {
outputStream.write ("Mary had a little lamb, its fleece was white as snow".getBytes());
}
outputStream.write(0);
byte[] input = outputStream.toByteArray();
input[input.length - 1] = input[0];
int[] output = new int[input.length];
new BZip2DivSufSort(input, output, input.length - 1).bwt();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < output.length; i++) {
sb.append ((char)output[i]);
}
assertEquals (expectedData, sb.toString());
}
}
libbzip2-java-0.9.1/src/test/TestBitInputStream.java 0000644 0001750 0001750 00000011540 11745741314 022266 0 ustar osallou osallou package test;
import static org.junit.Assert.*;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import org.itadaki.bzip2.BitInputStream;
import org.junit.Test;
/**
* Tests BitInputStream
*/
public class TestBitInputStream {
// Boolean
/**
* Test reading 8 zeroes
* @throws IOException
*/
@Test
public void testBooleanFalse8() throws IOException {
byte[] testData = { 0 };
BitInputStream inputStream = new BitInputStream (new ByteArrayInputStream (testData));
for (int i = 0; i < 8; i++) {
assertFalse (inputStream.readBoolean());
}
}
/**
* Test reading 8 ones
* @throws IOException
*/
@Test
public void testBooleanTrue8() throws IOException {
byte[] testData = { (byte)0xff };
BitInputStream inputStream = new BitInputStream (new ByteArrayInputStream (testData));
for (int i = 0; i < 8; i++) {
assertTrue (inputStream.readBoolean());
}
}
/**
* Test reading a single 1 in any position as a boolean
* @throws IOException
*/
@Test
public void testBooleanSingleOne() throws IOException {
for (int i = 0; i < 8; i++) {
byte[] testData = { (byte)(1 << (7 - i)) };
BitInputStream inputStream = new BitInputStream (new ByteArrayInputStream (testData));
for (int j = 0; j < 8; j++) {
if (j == i) {
assertTrue (inputStream.readBoolean());
} else {
assertFalse (inputStream.readBoolean());
}
}
}
}
/**
* Test reaching the end of the stream reading a boolean
* @throws IOException
*/
@Test(expected=IOException.class)
public void testBooleanEndOfStream() throws IOException {
byte[] testData = { };
BitInputStream inputStream = new BitInputStream (new ByteArrayInputStream (testData));
inputStream.readBoolean();
}
// Unary
/**
* Test reading unary 0
* @throws IOException
*/
@Test
public void testUnaryZero() throws IOException {
byte[] testData = { 0x00 };
BitInputStream inputStream = new BitInputStream (new ByteArrayInputStream (testData));
assertEquals (0, inputStream.readUnary());
}
/**
* Test reading unary 0
* @throws IOException
*/
@Test
public void testUnaryOne() throws IOException {
byte[] testData = { (byte)(1 << 7) };
BitInputStream inputStream = new BitInputStream (new ByteArrayInputStream (testData));
assertEquals (1, inputStream.readUnary());
}
/**
* Test reading unary 0
* @throws IOException
*/
@Test
public void testUnary31() throws IOException {
byte[] testData = { (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xfe };
BitInputStream inputStream = new BitInputStream (new ByteArrayInputStream (testData));
assertEquals (31, inputStream.readUnary());
}
/**
* Test reaching the end of the stream reading a unary number
* @throws IOException
*/
@Test(expected=IOException.class)
public void testUnaryEndOfStream() throws IOException {
byte[] testData = { };
BitInputStream inputStream = new BitInputStream (new ByteArrayInputStream (testData));
inputStream.readUnary();
}
// Bits
/**
* Test reading a single 0 as bits
* @throws IOException
*/
@Test
public void testBits1_0() throws IOException {
byte[] testData = { (byte)0x00 };
BitInputStream inputStream = new BitInputStream (new ByteArrayInputStream (testData));
assertEquals (0, inputStream.readBits(1));
}
/**
* Test reading a single 1 as bits
* @throws IOException
*/
@Test
public void testBits1_1() throws IOException {
byte[] testData = { (byte)(1 << 7) };
BitInputStream inputStream = new BitInputStream (new ByteArrayInputStream (testData));
assertEquals (1, inputStream.readBits(1));
}
/**
* Test reading 23 bits
* @throws IOException
*/
@Test
public void testBits23() throws IOException {
byte[] testData = { 0x02, 0x03, 0x04 };
BitInputStream inputStream = new BitInputStream (new ByteArrayInputStream (testData));
assertEquals (0x020304 >> 1, inputStream.readBits(23));
}
/**
* Test reaching the end of the stream reading bits
* @throws IOException
*/
@Test(expected=IOException.class)
public void testBitsEndOfStream() throws IOException {
byte[] testData = { };
BitInputStream inputStream = new BitInputStream (new ByteArrayInputStream (testData));
inputStream.readBits(1);
}
// Integer
/**
* Test reading an integer
* @throws IOException
*/
@Test
public void testInteger() throws IOException {
byte[] testData = { 0x12, 0x34, 0x56, 0x78 };
BitInputStream inputStream = new BitInputStream (new ByteArrayInputStream (testData));
assertEquals (0x12345678, inputStream.readInteger());
}
/**
* Test reaching the end of the stream reading an integer
* @throws IOException
*/
@Test(expected=IOException.class)
public void testIntegerEndOfStream() throws IOException {
byte[] testData = { };
BitInputStream inputStream = new BitInputStream (new ByteArrayInputStream (testData));
inputStream.readInteger();
}
}
libbzip2-java-0.9.1/src/test/TestBitOutputStream.java 0000644 0001750 0001750 00000013571 11745741314 022475 0 ustar osallou osallou package test;
import static org.junit.Assert.*;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import org.itadaki.bzip2.BitOutputStream;
import org.junit.Test;
/**
* Tests BitOutputStream
*/
public class TestBitOutputStream {
// Boolean
/**
* Test writing 8 zeroes
* @throws IOException
*/
@Test
public void testBooleanFalse8() throws IOException {
byte[] expected = { 0 };
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
BitOutputStream outputStream = new BitOutputStream (byteArrayOutputStream);
for (int i = 0; i < 8; i++) {
outputStream.writeBoolean (false);
}
outputStream.flush();
assertArrayEquals (expected, byteArrayOutputStream.toByteArray());
}
/**
* Test writing 8 ones
* @throws IOException
*/
@Test
public void testBooleanTrue8() throws IOException {
byte[] expected = { (byte)0xff };
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
BitOutputStream outputStream = new BitOutputStream (byteArrayOutputStream);
for (int i = 0; i < 8; i++) {
outputStream.writeBoolean (true);
}
outputStream.flush();
assertArrayEquals (expected, byteArrayOutputStream.toByteArray());
}
/**
* Test writing a single 1 in any position as a boolean
* @throws IOException
*/
@Test
public void testBooleanSingleOne() throws IOException {
for (int i = 0; i < 8; i++) {
byte[] expected = { (byte)(1 << (7 - i)) };
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
BitOutputStream outputStream = new BitOutputStream (byteArrayOutputStream);
for (int j = 0; j < 8; j++) {
outputStream.writeBoolean (j == i);
}
outputStream.flush();
assertArrayEquals (expected, byteArrayOutputStream.toByteArray());
}
}
// Unary
/**
* Test writing unary 0
* @throws IOException
*/
@Test
public void testUnaryZero() throws IOException {
byte[] expected = { 0x00 };
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
BitOutputStream outputStream = new BitOutputStream (byteArrayOutputStream);
outputStream.writeUnary (0);
outputStream.flush();
assertArrayEquals (expected, byteArrayOutputStream.toByteArray());
}
/**
* Test writing unary 0
* @throws IOException
*/
@Test
public void testUnaryOne() throws IOException {
byte[] expected = { (byte)(1 << 7) };
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
BitOutputStream outputStream = new BitOutputStream (byteArrayOutputStream);
outputStream.writeUnary (1);
outputStream.flush();
assertArrayEquals (expected, byteArrayOutputStream.toByteArray());
}
/**
* Test writing unary 0
* @throws IOException
*/
@Test
public void testUnary31() throws IOException {
byte[] expected = { (byte)0xff, (byte)0xff, (byte)0xff, (byte)0xfe };
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
BitOutputStream outputStream = new BitOutputStream (byteArrayOutputStream);
outputStream.writeUnary (31);
outputStream.flush();
assertArrayEquals (expected, byteArrayOutputStream.toByteArray());
}
// Bits
/**
* Test writing a single 0 as bits
* @throws IOException
*/
@Test
public void testBits1_0() throws IOException {
byte[] expected = { (byte)0x00 };
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
BitOutputStream outputStream = new BitOutputStream (byteArrayOutputStream);
outputStream.writeBits (1, 0);
outputStream.flush();
assertArrayEquals (expected, byteArrayOutputStream.toByteArray());
}
/**
* Test writing a single 1 as bits
* @throws IOException
*/
@Test
public void testBits1_1() throws IOException {
byte[] expected = { (byte)(1 << 7) };
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
BitOutputStream outputStream = new BitOutputStream (byteArrayOutputStream);
outputStream.writeBits (1, 1);
outputStream.flush();
assertArrayEquals (expected, byteArrayOutputStream.toByteArray());
}
/**
* Test writing 23 bits
* @throws IOException
*/
@Test
public void testBits23() throws IOException {
byte[] expected = { 0x02, 0x03, 0x04 };
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
BitOutputStream outputStream = new BitOutputStream (byteArrayOutputStream);
outputStream.writeBits (23, 0x020304 >> 1);
outputStream.flush();
assertArrayEquals (expected, byteArrayOutputStream.toByteArray());
}
/**
* Test writing 24 bits
* @throws IOException
*/
@Test
public void testBits24() throws IOException {
byte[] expected = { (byte)0xff, (byte)0xff, (byte)0xff };
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
BitOutputStream outputStream = new BitOutputStream (byteArrayOutputStream);
outputStream.writeBits (24, 0xffffff);
outputStream.flush();
assertArrayEquals (expected, byteArrayOutputStream.toByteArray());
}
/**
* Test write masking
* @throws IOException
*/
@Test
public void testBitsWriteMasking() throws IOException {
byte[] expected = { 0x01, (byte)0xff, (byte)0xff };
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
BitOutputStream outputStream = new BitOutputStream (byteArrayOutputStream);
outputStream.writeBits (7, 0);
outputStream.writeBits (17, 0xfffff);
outputStream.flush();
assertArrayEquals (expected, byteArrayOutputStream.toByteArray());
}
// Integer
/**
* Test writing an integer
* @throws IOException
*/
@Test
public void testInteger() throws IOException {
byte[] expected = { 0x12, 0x34, 0x56, 0x78 };
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
BitOutputStream outputStream = new BitOutputStream (byteArrayOutputStream);
outputStream.writeInteger(0x12345678);
outputStream.flush();
assertArrayEquals (expected, byteArrayOutputStream.toByteArray());
}
}
libbzip2-java-0.9.1/src/test/TestHuffmanAllocator.java 0000644 0001750 0001750 00000007375 11745741314 022614 0 ustar osallou osallou package test;
import static org.junit.Assert.*;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import org.itadaki.bzip2.HuffmanAllocator;
import org.junit.Test;
/**
* Tests HuffmanAllocator
*/
public class TestHuffmanAllocator {
/**
* Fibonacci sequence
*/
private static int[] fibonacci = {
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181,
6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 832040,
1346269, 2178309, 3524578, 5702887, 9227465, 14930352
};
/**
* @throws IOException
*/
@Test
public void testShort1() throws IOException {
int[] expectedLengths = new int[] {
1
};
int[] frequencies = new int[] { 1 };
HuffmanAllocator.allocateHuffmanCodeLengths (frequencies, 32);
assertArrayEquals (expectedLengths, frequencies);
}
/**
* @throws IOException
*/
@Test
public void testShort2() throws IOException {
int[] expectedLengths = new int[] {
1, 1
};
int[] frequencies = new int[] { 1, 1 };
HuffmanAllocator.allocateHuffmanCodeLengths (frequencies, 32);
assertArrayEquals (expectedLengths, frequencies);
}
/**
* @throws IOException
*/
@Test
public void testRegular1() throws IOException {
int[] expectedLengths = new int[] {
3, 3, 2, 2, 2
};
int[] frequencies = new int[] { 1, 1, 1, 1, 1 };
HuffmanAllocator.allocateHuffmanCodeLengths (frequencies, 32);
assertArrayEquals (expectedLengths, frequencies);
}
/**
* @throws IOException
*/
@Test
public void testBoundary1() throws IOException {
int[] expectedLengths = new int[] {
3, 3, 3, 3, 2, 2
};
int[] frequencies = new int[] { 0, 0, 1, 1, 1, 1 };
HuffmanAllocator.allocateHuffmanCodeLengths (frequencies, 3);
assertArrayEquals (expectedLengths, frequencies);
}
/**
* @throws IOException
*/
@Test
public void testFibonacci1() throws IOException {
int[] expectedLengths = new int[] {
20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 20, 19, 19, 18, 17, 16,
16, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1
};
int[] frequencies = Arrays.copyOf (fibonacci, 36);
HuffmanAllocator.allocateHuffmanCodeLengths (frequencies, 20);
assertArrayEquals (expectedLengths, frequencies);
}
/**
* @throws IOException
*/
@Test
public void testFibonacci2() throws IOException {
int[] expectedLengths = new int[] {
20, 20, 19, 19, 19, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1,
};
int[] frequencies = Arrays.copyOf (fibonacci, 22);
HuffmanAllocator.allocateHuffmanCodeLengths (frequencies, 20);
assertArrayEquals (expectedLengths, frequencies);
}
/**
* @throws IOException
*/
@Test
public void testFibonacci3() throws IOException {
int[] expectedLengths = new int[] {
20, 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1,
};
int[] frequencies = Arrays.copyOf (fibonacci, 21);
HuffmanAllocator.allocateHuffmanCodeLengths (frequencies, 20);
assertArrayEquals (expectedLengths, frequencies);
}
/**
* @throws IOException
*/
@Test
public void testFibonacci4() throws IOException {
int[] expectedLengths = new int[] {
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, 5, 5, 5, 4, 3, 2
};
int[] frequencies = Arrays.copyOf (fibonacci, 36);
HuffmanAllocator.allocateHuffmanCodeLengths (frequencies, 6);
assertArrayEquals (expectedLengths, frequencies);
}
/**
* Pointless test to bump coverage to 100%
* @throws Exception
*/
@Test
public void testPointlessConstructorTestCoverage() throws Exception {
Constructor> c[] = HuffmanAllocator.class.getDeclaredConstructors();
c[0].setAccessible (true);
c[0].newInstance ((Object[])null);
}
}
libbzip2-java-0.9.1/src/test/TestBZip2HuffmanStageDecoder.java 0000644 0001750 0001750 00000002350 11745741314 024060 0 ustar osallou osallou package test;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import org.itadaki.bzip2.BZip2HuffmanStageDecoder;
import org.itadaki.bzip2.BitInputStream;
import org.itadaki.bzip2.BitOutputStream;
import org.junit.Test;
/**
* Tests BZip2HuffmanStageDecoder
*/
public class TestBZip2HuffmanStageDecoder {
/**
* Tests decoding an invalid symbol
* @throws Exception
*/
@Test(expected=IOException.class)
public void testExceptionOnInvalidSymbol() throws Exception {
byte[][] tableCodeLengths = { { 23, 23, 23, 22, 22, 21, 21, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 3, 3, 3, 3, 3, 3 } };
byte[] selectors = new byte[1024];
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
BitOutputStream bitOutputStream = new BitOutputStream (outputStream);
bitOutputStream.writeBits (23, 8388607); // This value would be the 4th 23-length code
bitOutputStream.flush();
BitInputStream bitInputStream = new BitInputStream (new ByteArrayInputStream (outputStream.toByteArray()));
BZip2HuffmanStageDecoder decoder = new BZip2HuffmanStageDecoder (bitInputStream, tableCodeLengths[0].length, tableCodeLengths, selectors);
decoder.nextSymbol();
}
}
libbzip2-java-0.9.1/src/test/TestBZip2OutputStream.java 0000644 0001750 0001750 00000072352 11745741314 022707 0 ustar osallou osallou package test;
import static org.junit.Assert.*;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Random;
import org.itadaki.bzip2.BZip2InputStream;
import org.itadaki.bzip2.BZip2OutputStream;
import org.junit.Test;
/**
* Tests BZip2OutputStream
*/
public class TestBZip2OutputStream {
/**
* @throws IOException
*/
@Test
public void testEmpty() throws IOException {
byte[] testData = new byte [0];
// Compress
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
BZip2OutputStream output = new BZip2OutputStream (byteOutput);
output.write (testData);
output.close();
// Test EOF
ByteArrayInputStream byteInput = new ByteArrayInputStream (byteOutput.toByteArray());
BZip2InputStream input = new BZip2InputStream (byteInput, false);
assertEquals (-1, input.read());
}
/**
* @throws IOException
*/
@Test
public void testOneByte() throws IOException {
byte[] testData = new byte[] { 'A' };
// Compress
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
BZip2OutputStream output = new BZip2OutputStream (byteOutput);
output.write (testData);
output.close();
// Decompress
ByteArrayInputStream byteInput = new ByteArrayInputStream (byteOutput.toByteArray());
BZip2InputStream input = new BZip2InputStream (byteInput, false);
byte[] decodedTestData = new byte [testData.length];
input.read(decodedTestData, 0, decodedTestData.length);
// Compare
assertArrayEquals(testData, decodedTestData);
assertEquals (-1, input.read());
}
/**
* @throws IOException
*/
@Test
public void testTwoBytes() throws IOException {
byte[] testData = new byte[] { 'B', 'A' };
// Compress
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
BZip2OutputStream output = new BZip2OutputStream (byteOutput);
output.write (testData);
output.close();
// Decompress
ByteArrayInputStream byteInput = new ByteArrayInputStream (byteOutput.toByteArray());
BZip2InputStream input = new BZip2InputStream (byteInput, false);
byte[] decodedTestData = new byte [testData.length];
input.read(decodedTestData, 0, decodedTestData.length);
// Compare
assertArrayEquals(testData, decodedTestData);
assertEquals (-1, input.read());
}
/**
* @throws IOException
*/
@Test
public void testRegular1() throws IOException {
byte[] testData = "Mary had a little lamb, its fleece was white as snow".getBytes();
// Compress
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
BZip2OutputStream output = new BZip2OutputStream (byteOutput);
output.write (testData);
output.close();
// Decompress
ByteArrayInputStream byteInput = new ByteArrayInputStream (byteOutput.toByteArray());
BZip2InputStream input = new BZip2InputStream (byteInput, false);
byte[] decodedTestData = new byte [testData.length];
input.read(decodedTestData, 0, decodedTestData.length);
// Compare
assertArrayEquals(testData, decodedTestData);
assertEquals (-1, input.read());
}
/**
* @throws IOException
*/
@Test
public void testRegular2() throws IOException {
byte[] testData = "Mary had a little lamb, its fleece was white as snow".getBytes();
// Compress
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
BZip2OutputStream output = new BZip2OutputStream (byteOutput);
for (int i = 0; i < testData.length; i++) {
output.write (testData[i]);
}
output.close();
// Decompress
ByteArrayInputStream byteInput = new ByteArrayInputStream (byteOutput.toByteArray());
BZip2InputStream input = new BZip2InputStream (byteInput, false);
byte[] decodedTestData = new byte [testData.length];
input.read(decodedTestData, 0, decodedTestData.length);
// Compare
assertArrayEquals(testData, decodedTestData);
assertEquals (-1, input.read());
}
/**
* @throws IOException
*/
@Test
public void testHeaderless() throws IOException {
byte[] testData = "Mary had a little lamb, its fleece was white as snow".getBytes();
// Compress
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
BZip2OutputStream output = new BZip2OutputStream (byteOutput);
output.write (testData);
output.close();
// Decompress
byte[] compressedData = byteOutput.toByteArray();
ByteArrayInputStream byteInput = new ByteArrayInputStream (Arrays.copyOfRange (compressedData, 2, compressedData.length));
BZip2InputStream input = new BZip2InputStream (byteInput, true);
byte[] decodedTestData = new byte [testData.length];
input.read(decodedTestData, 0, decodedTestData.length);
// Compare
assertArrayEquals(testData, decodedTestData);
assertEquals (-1, input.read());
}
/**
* @throws IOException
*/
@Test
public void testReadPastEnd() throws IOException {
byte[] testData = "Mary had a little lamb, its fleece was white as snow".getBytes();
// Compress
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
BZip2OutputStream output = new BZip2OutputStream (byteOutput);
output.write (testData);
output.close();
// Decompress
ByteArrayInputStream byteInput = new ByteArrayInputStream (byteOutput.toByteArray());
BZip2InputStream input = new BZip2InputStream (byteInput, false);
byte[] decodedTestData = new byte [testData.length];
input.read(decodedTestData, 0, decodedTestData.length);
// Test
assertEquals (-1, input.read());
assertEquals (-1, input.read());
}
/**
* @throws IOException
*/
@Test
public void testReadAfterClose() throws IOException {
byte[] testData = "Mary had a little lamb, its fleece was white as snow".getBytes();
// Compress
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
BZip2OutputStream output = new BZip2OutputStream (byteOutput);
output.write (testData);
output.close();
// Decompress
ByteArrayInputStream byteInput = new ByteArrayInputStream (byteOutput.toByteArray());
BZip2InputStream input = new BZip2InputStream (byteInput, false);
// Test
input.close();
try {
input.read();
} catch (IOException e) {
assertEquals ("Stream closed", e.getMessage());
return;
}
fail();
}
/**
* Test coverage : InputStream throws an exception during BZip2InputStream.close()
* @throws IOException
*/
@Test(expected=IOException.class)
public void testExceptionDuringClose() throws IOException {
byte[] testData = "Mary had a little lamb, its fleece was white as snow".getBytes();
// Compress
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
BZip2OutputStream output = new BZip2OutputStream (byteOutput);
output.write (testData);
output.close();
// Decompress
ByteArrayInputStream byteInput = new ByteArrayInputStream (byteOutput.toByteArray()) {
@Override
public void close() throws IOException {
throw new IOException();
}
};
BZip2InputStream input = new BZip2InputStream (byteInput, false);
// Test
input.close();
}
/**
* @throws IOException
*/
@Test
public void testWriteAfterClose1() throws IOException {
byte[] testData = "Mary had a little lamb, its fleece was white as snow".getBytes();
// Compress
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
BZip2OutputStream output = new BZip2OutputStream (byteOutput);
output.write (testData);
output.close();
// Test
try {
output.write (testData);
} catch (IOException e) {
assertEquals ("Stream closed", e.getMessage());
return;
}
fail();
}
/**
* @throws IOException
*/
@Test
public void testWriteAfterClose2() throws IOException {
byte[] testData = "Mary had a little lamb, its fleece was white as snow".getBytes();
// Compress
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
BZip2OutputStream output = new BZip2OutputStream (byteOutput);
output.write (testData);
output.close();
// Test
try {
output.write (1);
} catch (IOException e) {
assertEquals ("Stream closed", e.getMessage());
return;
}
fail();
}
/**
* @throws IOException
*/
@Test
public void testWriteAfterFinish1() throws IOException {
byte[] testData = "Mary had a little lamb, its fleece was white as snow".getBytes();
// Compress
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
BZip2OutputStream output = new BZip2OutputStream (byteOutput);
output.write (testData);
output.finish();
// Test
try {
output.write (testData);
} catch (IOException e) {
assertEquals ("Write beyond end of stream", e.getMessage());
return;
}
fail();
}
/**
* @throws IOException
*/
@Test
public void testWriteAfterFinish2() throws IOException {
byte[] testData = "Mary had a little lamb, its fleece was white as snow".getBytes();
// Compress
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
BZip2OutputStream output = new BZip2OutputStream (byteOutput);
output.write (testData);
output.finish();
// Test
try {
output.write (1);
} catch (IOException e) {
assertEquals ("Write beyond end of stream", e.getMessage());
return;
}
fail();
}
/**
* Test coverage : OutputStream throws an exception during BZip2OutputStream.close()
* @throws IOException
*/
@Test(expected=IOException.class)
public void testExceptionDuringFinish() throws IOException {
byte[] testData = new byte[] { 'A' };
// Compress
OutputStream byteOutput = new OutputStream() {
private int count = 0;
@Override
public void write (int b) throws IOException {
if (++this.count == 35) {
throw new IOException();
}
}
};
BZip2OutputStream output = new BZip2OutputStream (byteOutput);
output.write (testData);
output.close();
}
/**
* @throws IOException
*/
@Test(expected=IllegalArgumentException.class)
public void testNullInputStream() throws IOException {
new BZip2InputStream (null, false);
}
/**
* @throws IOException
*/
@Test(expected=IllegalArgumentException.class)
public void testNullOutputStream() throws IOException {
new BZip2OutputStream (null, 1);
}
/**
* @throws IOException
*/
@Test(expected=IllegalArgumentException.class)
public void testOutputStreamInvalidBlockSize1() throws IOException {
new BZip2OutputStream (new ByteArrayOutputStream(), 0);
}
/**
* @throws IOException
*/
@Test(expected=IllegalArgumentException.class)
public void testOutputStreamInvalidBlockSize2() throws IOException {
new BZip2OutputStream (new ByteArrayOutputStream(), 10);
}
/**
* @throws IOException
*/
@Test
public void test3Tables() throws IOException {
byte[] testData = new byte [500];
// Create test block
Random random = new Random (1234);
random.nextBytes (testData);
// Compress
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
BZip2OutputStream output = new BZip2OutputStream (byteOutput);
output.write (testData);
output.close();
// Decompress
ByteArrayInputStream byteInput = new ByteArrayInputStream (byteOutput.toByteArray());
BZip2InputStream input = new BZip2InputStream (byteInput, false);
byte[] decodedTestData = new byte [testData.length];
input.read (decodedTestData, 0, decodedTestData.length);
// Compare
assertArrayEquals (testData, decodedTestData);
assertEquals (-1, input.read());
}
/**
* @throws IOException
*/
@Test
public void test4Tables() throws IOException {
byte[] testData = new byte [1100];
// Create test block
Random random = new Random (1234);
random.nextBytes (testData);
// Compress
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
BZip2OutputStream output = new BZip2OutputStream (byteOutput);
output.write (testData);
output.close();
// Decompress
ByteArrayInputStream byteInput = new ByteArrayInputStream (byteOutput.toByteArray());
BZip2InputStream input = new BZip2InputStream (byteInput, false);
byte[] decodedTestData = new byte [testData.length];
input.read (decodedTestData, 0, decodedTestData.length);
// Compare
assertArrayEquals (testData, decodedTestData);
assertEquals (-1, input.read());
}
/**
* @throws IOException
*/
@Test
public void test5Tables() throws IOException {
byte[] testData = new byte [2300];
// Create test block
Random random = new Random (1234);
random.nextBytes (testData);
// Compress
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
BZip2OutputStream output = new BZip2OutputStream (byteOutput);
output.write (testData);
output.close();
// Decompress
ByteArrayInputStream byteInput = new ByteArrayInputStream (byteOutput.toByteArray());
BZip2InputStream input = new BZip2InputStream (byteInput, false);
byte[] decodedTestData = new byte [testData.length];
input.read (decodedTestData, 0, decodedTestData.length);
// Compare
assertArrayEquals (testData, decodedTestData);
assertEquals (-1, input.read());
}
/**
* @throws IOException
*/
@Test
public void testLargeRandom() throws IOException {
byte[] testData = new byte [1048576];
// Create test block
Random random = new Random (1234);
random.nextBytes (testData);
// Compress
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
BZip2OutputStream output = new BZip2OutputStream (byteOutput);
output.write (testData);
output.close();
// Decompress
ByteArrayInputStream byteInput = new ByteArrayInputStream (byteOutput.toByteArray());
BZip2InputStream input = new BZip2InputStream (byteInput, false);
byte[] decodedTestData = new byte [testData.length];
int remaining = testData.length;
while (remaining > 0) {
int read = input.read (decodedTestData, testData.length - remaining, remaining);
if (read > 0) {
remaining -= read;
} else {
break;
}
}
// Compare
assertArrayEquals (testData, decodedTestData);
assertEquals (-1, input.read());
}
/**
* @throws IOException
*/
@Test
public void testLargeRandomWriteSingleBytes() throws IOException {
byte[] testData = new byte [1048576];
// Create test block
Random random = new Random (1234);
random.nextBytes (testData);
// Compress
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
BZip2OutputStream output = new BZip2OutputStream (byteOutput);
for (int i = 0; i < testData.length; i++) {
output.write (testData[i]);
}
output.close();
// Decompress
ByteArrayInputStream byteInput = new ByteArrayInputStream (byteOutput.toByteArray());
BZip2InputStream input = new BZip2InputStream (byteInput, false);
byte[] decodedTestData = new byte [testData.length];
int remaining = testData.length;
while (remaining > 0) {
int read = input.read (decodedTestData, testData.length - remaining, remaining);
if (read > 0) {
remaining -= read;
} else {
break;
}
}
// Compare
assertArrayEquals (testData, decodedTestData);
assertEquals (-1, input.read());
}
/**
* @throws IOException
*/
@Test
public void testLargeRandomReadSingleBytes() throws IOException {
byte[] testData = new byte [1048576];
// Create test block
Random random = new Random (1234);
random.nextBytes (testData);
// Compress
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
BZip2OutputStream output = new BZip2OutputStream (byteOutput);
output.write (testData);
output.close();
// Decompress
ByteArrayInputStream byteInput = new ByteArrayInputStream (byteOutput.toByteArray());
BZip2InputStream input = new BZip2InputStream (byteInput, false);
byte[] decodedTestData = new byte [testData.length];
for (int i = 0; i < testData.length; i++) {
decodedTestData[i] = (byte)input.read();
}
// Compare
assertArrayEquals (testData, decodedTestData);
assertEquals (-1, input.read());
}
/**
* @throws IOException
*/
@Test
public void testPartRandom() throws IOException {
byte[] testData = new byte [12345];
// Create test block
Random random = new Random (1234);
random.nextBytes (testData);
for (int i = 0; i < 512; i++) {
testData[i] = (byte)123;
}
// Compress
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
BZip2OutputStream output = new BZip2OutputStream (byteOutput);
output.write (testData);
output.close();
// Decompress
ByteArrayInputStream byteInput = new ByteArrayInputStream (byteOutput.toByteArray());
BZip2InputStream input = new BZip2InputStream (byteInput, false);
byte[] decodedTestData = new byte [testData.length];
input.read (decodedTestData, 0, decodedTestData.length);
// Compare
assertArrayEquals (testData, decodedTestData);
assertEquals (-1, input.read());
}
/**
* @throws IOException
*/
@Test
public void testCompressible() throws IOException {
byte[] testData = new byte [10000];
// Create test block
Random random = new Random(1234);
for (int i = 0; i < testData.length; i++) {
testData[i] = ((i % 4) != 0) ? 0 : (byte)random.nextInt();
}
// Compress
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
BZip2OutputStream output = new BZip2OutputStream (byteOutput);
output.write (testData);
output.close();
// Decompress
ByteArrayInputStream byteInput = new ByteArrayInputStream (byteOutput.toByteArray());
BZip2InputStream input = new BZip2InputStream (byteInput, false);
byte[] decodedTestData = new byte [testData.length];
input.read (decodedTestData, 0, decodedTestData.length);
// Compare
assertArrayEquals (testData, decodedTestData);
assertEquals (-1, input.read());
}
/**
* @throws IOException
*/
@Test
public void testLongBlank() throws IOException {
// Blank test block
byte[] testData = new byte [100000];
// Compress
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
BZip2OutputStream output = new BZip2OutputStream (byteOutput);
output.write (testData);
output.close();
// Decompress
ByteArrayInputStream byteInput = new ByteArrayInputStream (byteOutput.toByteArray());
BZip2InputStream input = new BZip2InputStream (byteInput, false);
byte[] decodedTestData = new byte [testData.length];
input.read (decodedTestData, 0, decodedTestData.length);
// Compare
assertArrayEquals (testData, decodedTestData);
assertEquals (-1, input.read());
}
/**
* @throws IOException
*/
@Test
public void testLongSame() throws IOException {
byte[] testData = new byte [100000];
// Create test block
Arrays.fill (testData, (byte)123);
// Compress
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
BZip2OutputStream output = new BZip2OutputStream (byteOutput);
output.write (testData);
output.close();
// Decompress
ByteArrayInputStream byteInput = new ByteArrayInputStream (byteOutput.toByteArray());
BZip2InputStream input = new BZip2InputStream (byteInput, false);
byte[] decodedTestData = new byte [testData.length];
input.read (decodedTestData, 0, decodedTestData.length);
// Compare
assertArrayEquals (testData, decodedTestData);
assertEquals (-1, input.read());
}
/**
* @throws IOException
*/
@Test
public void testRandomised() throws IOException {
// Test block
byte[] compressedData = new byte [] {
0x42, 0x5a, 0x68, 0x39, 0x31, 0x41, 0x59, 0x26, 0x53, 0x59, (byte)0xf5, (byte)0xdc,
0x6d, (byte)0x8a, (byte)0x80, 0x00, 0x65, (byte)0x84, 0x00, 0x38, 0x00, 0x20, 0x00,
0x30, (byte)0xcc, 0x05, 0x29, (byte)0xa6, (byte)0xd5, 0x55, 0x58, 0x01, (byte)0xe2,
(byte)0xee, 0x48, (byte)0xa7, 0x0a, 0x12, 0x1e, (byte)0xbb, (byte)0x8d, (byte)0xb1,
0x40
};
byte[] uncompressedData = new byte[1024];
for (int i = 0; i < 1024;) {
uncompressedData[i++] = 'A';
uncompressedData[i++] = 'B';
}
// Decompress
ByteArrayInputStream byteInput = new ByteArrayInputStream (compressedData);
BZip2InputStream input = new BZip2InputStream (byteInput, false);
byte[] decodedTestData = new byte [uncompressedData.length];
input.read (decodedTestData, 0, decodedTestData.length);
// Compare
assertArrayEquals (uncompressedData, decodedTestData);
assertEquals (-1, input.read());
}
/**
* @throws IOException
*/
@Test
public void testInvalidHeader() throws IOException {
// Create test block
byte[] testData = new byte [1000];
Random random = new Random (1234);
random.nextBytes (testData);
// Compress
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
BZip2OutputStream output = new BZip2OutputStream (byteOutput);
output.write (testData);
output.close();
// Decompress
byte[] compressedData = byteOutput.toByteArray();
compressedData[0] = '1';
ByteArrayInputStream byteInput = new ByteArrayInputStream (compressedData);
BZip2InputStream input = new BZip2InputStream (byteInput, false);
byte[] decodedTestData = new byte [testData.length];
try {
input.read (decodedTestData, 0, decodedTestData.length);
fail();
} catch (IOException e) {
assertEquals ("Invalid BZip2 header", e.getMessage());
return;
}
fail();
}
/**
* @throws IOException
*/
@Test
public void testInvalidHeaderSecondRead() throws IOException {
// Create test block
byte[] testData = new byte [1000];
Random random = new Random (1234);
random.nextBytes (testData);
// Compress
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
BZip2OutputStream output = new BZip2OutputStream (byteOutput);
output.write (testData);
output.close();
// Decompress
byte[] compressedData = byteOutput.toByteArray();
compressedData[0] = '1';
ByteArrayInputStream byteInput = new ByteArrayInputStream (compressedData);
BZip2InputStream input = new BZip2InputStream (byteInput, false);
byte[] decodedTestData = new byte [testData.length];
try {
input.read (decodedTestData, 0, decodedTestData.length);
fail();
} catch (IOException e) {
assertEquals (-1, input.read());
return;
}
fail();
}
/**
* @throws IOException
*/
@Test
public void testInvalidBlockSize1() throws IOException {
// Create test block
byte[] testData = new byte [200000];
Random random = new Random (1234);
random.nextBytes (testData);
// Compress
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
BZip2OutputStream output = new BZip2OutputStream (byteOutput);
output.write (testData);
output.close();
// Decompress
byte[] compressedData = byteOutput.toByteArray();
compressedData[3] = '1';
ByteArrayInputStream byteInput = new ByteArrayInputStream (compressedData);
BZip2InputStream input = new BZip2InputStream (byteInput, false);
byte[] decodedTestData = new byte [testData.length];
try {
input.read (decodedTestData, 0, decodedTestData.length);
fail();
} catch (IOException e) {
assertEquals ("BZip2 block exceeds declared block size", e.getMessage());
return;
}
fail();
}
/**
* @throws IOException
*/
@Test
public void testInvalidBlockSize2() throws IOException {
// Create test block
byte[] testData = new byte [200000];
Random random = new Random (1234);
random.nextBytes (testData);
Arrays.fill(testData, 100000, testData.length, (byte)0);
// Compress
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
BZip2OutputStream output = new BZip2OutputStream (byteOutput);
output.write (testData);
output.close();
// Decompress
byte[] compressedData = byteOutput.toByteArray();
compressedData[3] = '1';
ByteArrayInputStream byteInput = new ByteArrayInputStream (compressedData);
BZip2InputStream input = new BZip2InputStream (byteInput, false);
byte[] decodedTestData = new byte [testData.length];
try {
input.read (decodedTestData, 0, decodedTestData.length);
fail();
} catch (IOException e) {
assertEquals ("BZip2 block exceeds declared block size", e.getMessage());
return;
}
fail();
}
/**
* @throws IOException
*/
@Test
public void testInvalidBlockCRC() throws IOException {
// Create test block
byte[] testData = new byte [1000];
Random random = new Random (1234);
random.nextBytes (testData);
// Compress
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
BZip2OutputStream output = new BZip2OutputStream (byteOutput);
output.write (testData);
output.close();
// Decompress
byte[] compressedData = byteOutput.toByteArray();
compressedData[10] = -1;
ByteArrayInputStream byteInput = new ByteArrayInputStream (compressedData);
BZip2InputStream input = new BZip2InputStream (byteInput, false);
byte[] decodedTestData = new byte [testData.length];
try {
input.read (decodedTestData, 0, decodedTestData.length);
input.read();
fail();
} catch (IOException e) {
assertEquals ("BZip2 block CRC error", e.getMessage());
return;
}
fail();
}
/**
* @throws IOException
*/
@Test
public void testInvalidStartPointer() throws IOException {
// Create test block
byte[] testData = new byte [1000];
Random random = new Random (1234);
random.nextBytes (testData);
// Compress
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
BZip2OutputStream output = new BZip2OutputStream (byteOutput);
output.write (testData);
output.close();
// Decompress
byte[] compressedData = byteOutput.toByteArray();
compressedData[14] = -1;
ByteArrayInputStream byteInput = new ByteArrayInputStream (compressedData);
BZip2InputStream input = new BZip2InputStream (byteInput, false);
byte[] decodedTestData = new byte [testData.length];
try {
input.read (decodedTestData, 0, decodedTestData.length);
input.read();
fail();
} catch (IOException e) {
assertEquals ("BZip2 start pointer invalid", e.getMessage());
return;
}
fail();
}
/**
* @throws IOException
*/
@Test
public void testInvalidStreamCRC() throws IOException {
// Create test block
byte[] testData = new byte [1000];
Random random = new Random (1234);
random.nextBytes (testData);
// Compress
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
BZip2OutputStream output = new BZip2OutputStream (byteOutput);
output.write (testData);
output.close();
// Decompress
byte[] compressedData = byteOutput.toByteArray();
compressedData[compressedData.length - 2] = -1;
ByteArrayInputStream byteInput = new ByteArrayInputStream (compressedData);
BZip2InputStream input = new BZip2InputStream (byteInput, false);
byte[] decodedTestData = new byte [testData.length];
try {
input.read (decodedTestData, 0, decodedTestData.length);
input.read();
fail();
} catch (IOException e) {
assertEquals ("BZip2 stream CRC error", e.getMessage());
return;
}
fail();
}
/**
* @throws IOException
*/
@Test
public void testInvalidBlockHeader() throws IOException {
// Create test block
byte[] testData = new byte [1000];
Random random = new Random (1234);
random.nextBytes (testData);
// Compress
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
BZip2OutputStream output = new BZip2OutputStream (byteOutput);
output.write (testData);
output.close();
// Decompress
byte[] compressedData = byteOutput.toByteArray();
compressedData[compressedData.length - 6] = -1;
ByteArrayInputStream byteInput = new ByteArrayInputStream (compressedData);
BZip2InputStream input = new BZip2InputStream (byteInput, false);
byte[] decodedTestData = new byte [testData.length];
try {
input.read (decodedTestData, 0, decodedTestData.length);
input.read();
fail();
} catch (IOException e) {
assertEquals ("BZip2 stream format error", e.getMessage());
return;
}
fail();
}
/**
* @throws IOException
*/
@Test
public void testDecompressionBug1() throws IOException {
byte[] testData = new byte [49];
// Create test block
for (int i = 0; i < testData.length; i++) {
testData[i] = (byte)i;
}
// Compress
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
BZip2OutputStream output = new BZip2OutputStream (byteOutput);
output.write (testData);
output.close();
// Decompress
ByteArrayInputStream byteInput = new ByteArrayInputStream (byteOutput.toByteArray());
BZip2InputStream input = new BZip2InputStream (byteInput, false);
byte[] decodedTestData = new byte [testData.length];
input.read (decodedTestData, 0, decodedTestData.length);
// Compare
assertArrayEquals (testData, decodedTestData);
assertEquals (-1, input.read());
}
/**
* @throws IOException
*/
@Test
public void testDecompressionBug2() throws IOException {
byte[] testData = new byte [0];
// Compress
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
BZip2OutputStream output = new BZip2OutputStream (byteOutput);
output.write (testData);
output.close();
// Decompress
ByteArrayInputStream byteInput = new ByteArrayInputStream (byteOutput.toByteArray());
BZip2InputStream input = new BZip2InputStream (byteInput, false);
// Compare
assertEquals (-1, input.read());
assertEquals (-1, input.read());
}
/**
* @throws IOException
*/
@Test
public void testCompressionBug1() throws IOException {
byte[] testData = new byte [4];
// Compress
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
BZip2OutputStream output = new BZip2OutputStream (byteOutput);
output.write (testData);
output.close();
// Decompress
ByteArrayInputStream byteInput = new ByteArrayInputStream (byteOutput.toByteArray());
BZip2InputStream input = new BZip2InputStream (byteInput, false);
byte[] decodedTestData = new byte [testData.length];
input.read (decodedTestData, 0, decodedTestData.length);
// Compare
assertArrayEquals (testData, decodedTestData);
assertEquals (-1, input.read());
}
// TODO Test BZip2BlockCompressor#close write run at block limit
}
libbzip2-java-0.9.1/src/demo/ 0000755 0001750 0001750 00000000000 11745741315 015616 5 ustar osallou osallou libbzip2-java-0.9.1/src/demo/Compress.java 0000644 0001750 0001750 00000005077 11745741314 020264 0 ustar osallou osallou /*
* Copyright (c) 2011 Matthew Francis
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package demo;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.itadaki.bzip2.BZip2OutputStream;
/**
* A BZip2 file compressor. For demonstration purposes only
*/
public class Compress {
/**
* @param args
* @throws IOException
*/
public static void main (String[] args) throws IOException {
if (args.length == 0) {
System.err.println ("Demonstration BZip2 compressor\n\nUsage:\n java demo.Compress