View Javadoc
1   /*
2    * Copyright 2014 The Netty Project
3    *
4    * The Netty Project licenses this file to you under the Apache License,
5    * version 2.0 (the "License"); you may not use this file except in compliance
6    * with the License. You may obtain a copy of the License at:
7    *
8    *   https://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12   * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13   * License for the specific language governing permissions and limitations
14   * under the License.
15   */
16  package io.netty5.handler.codec.compression;
17  
18  /**
19   * An in-place, length restricted Canonical Huffman code length allocator.<br>
20   * Based on the algorithm proposed by R. L. Milidi'u, A. A. Pessoa and E. S. Laber in
21   * <a href="http://www-di.inf.puc-rio.br/~laber/public/spire98.ps">In-place Length-Restricted Prefix Coding</a>
22   * and incorporating additional ideas from the implementation of
23   * <a href="http://entropyware.info/shcodec/index.html">shcodec</a> by Simakov Alexander.
24   */
25  final class Bzip2HuffmanAllocator {
26      /**
27       * @param array The code length array
28       * @param i The input position
29       * @param nodesToMove The number of internal nodes to be relocated
30       * @return The smallest {@code k} such that {@code nodesToMove <= k <= i} and
31       *         {@code i <= (array[k] % array.length)}
32       */
33      private static int first(final int[] array, int i, final int nodesToMove) {
34          final int length = array.length;
35          final int limit = i;
36          int k = array.length - 2;
37  
38          while (i >= nodesToMove && array[i] % length > limit) {
39              k = i;
40              i -= limit - i + 1;
41          }
42          i = Math.max(nodesToMove - 1, i);
43  
44          while (k > i + 1) {
45              int temp = i + k >>> 1;
46              if (array[temp] % length > limit) {
47                  k = temp;
48              } else {
49                  i = temp;
50              }
51          }
52          return k;
53      }
54  
55      /**
56       * Fills the code array with extended parent pointers.
57       * @param array The code length array
58       */
59      private static void setExtendedParentPointers(final int[] array) {
60          final int length = array.length;
61          array[0] += array[1];
62  
63          for (int headNode = 0, tailNode = 1, topNode = 2; tailNode < length - 1; tailNode++) {
64              int temp;
65              if (topNode >= length || array[headNode] < array[topNode]) {
66                  temp = array[headNode];
67                  array[headNode++] = tailNode;
68              } else {
69                  temp = array[topNode++];
70              }
71  
72              if (topNode >= length || (headNode < tailNode && array[headNode] < array[topNode])) {
73                  temp += array[headNode];
74                  array[headNode++] = tailNode + length;
75              } else {
76                  temp += array[topNode++];
77              }
78              array[tailNode] = temp;
79          }
80      }
81  
82      /**
83       * Finds the number of nodes to relocate in order to achieve a given code length limit.
84       * @param array The code length array
85       * @param maximumLength The maximum bit length for the generated codes
86       * @return The number of nodes to relocate
87       */
88      private static int findNodesToRelocate(final int[] array, final int maximumLength) {
89          int currentNode = array.length - 2;
90          for (int currentDepth = 1; currentDepth < maximumLength - 1 && currentNode > 1; currentDepth++) {
91              currentNode =  first(array, currentNode - 1, 0);
92          }
93          return currentNode;
94      }
95  
96      /**
97       * A final allocation pass with no code length limit.
98       * @param array The code length array
99       */
100     private static void allocateNodeLengths(final int[] array) {
101         int firstNode = array.length - 2;
102         int nextNode = array.length - 1;
103 
104         for (int currentDepth = 1, availableNodes = 2; availableNodes > 0; currentDepth++) {
105             final int lastNode = firstNode;
106             firstNode = first(array, lastNode - 1, 0);
107 
108             for (int i = availableNodes - (lastNode - firstNode); i > 0; i--) {
109                 array[nextNode--] = currentDepth;
110             }
111 
112             availableNodes = (lastNode - firstNode) << 1;
113         }
114     }
115 
116     /**
117      * A final allocation pass that relocates nodes in order to achieve a maximum code length limit.
118      * @param array The code length array
119      * @param nodesToMove The number of internal nodes to be relocated
120      * @param insertDepth The depth at which to insert relocated nodes
121      */
122     private static void allocateNodeLengthsWithRelocation(final int[] array,
123                                                            final int nodesToMove, final int insertDepth) {
124         int firstNode = array.length - 2;
125         int nextNode = array.length - 1;
126         int currentDepth = insertDepth == 1 ? 2 : 1;
127         int nodesLeftToMove = insertDepth == 1 ? nodesToMove - 2 : nodesToMove;
128 
129         for (int availableNodes = currentDepth << 1; availableNodes > 0; currentDepth++) {
130             final int lastNode = firstNode;
131             firstNode = firstNode <= nodesToMove ? firstNode : first(array, lastNode - 1, nodesToMove);
132 
133             int offset = 0;
134             if (currentDepth >= insertDepth) {
135                 offset = Math.min(nodesLeftToMove, 1 << (currentDepth - insertDepth));
136             } else if (currentDepth == insertDepth - 1) {
137                 offset = 1;
138                 if (array[firstNode] == lastNode) {
139                     firstNode++;
140                 }
141             }
142 
143             for (int i = availableNodes - (lastNode - firstNode + offset); i > 0; i--) {
144                 array[nextNode--] = currentDepth;
145             }
146 
147             nodesLeftToMove -= offset;
148             availableNodes = (lastNode - firstNode + offset) << 1;
149         }
150     }
151 
152     /**
153      * Allocates Canonical Huffman code lengths in place based on a sorted frequency array.
154      * @param array On input, a sorted array of symbol frequencies; On output, an array of Canonical
155      *              Huffman code lengths
156      * @param maximumLength The maximum code length. Must be at least {@code ceil(log2(array.length))}
157      */
158     static void allocateHuffmanCodeLengths(final int[] array, final int maximumLength) {
159         switch (array.length) {
160             case 2:
161                 array[1] = 1;
162                 // fall through
163             case 1:
164                 array[0] = 1;
165                 return;
166         }
167 
168         /* Pass 1 : Set extended parent pointers */
169         setExtendedParentPointers(array);
170 
171         /* Pass 2 : Find number of nodes to relocate in order to achieve maximum code length */
172         int nodesToRelocate = findNodesToRelocate(array, maximumLength);
173 
174         /* Pass 3 : Generate code lengths */
175         if (array[0] % array.length >= nodesToRelocate) {
176             allocateNodeLengths(array);
177         } else {
178             int insertDepth = maximumLength - (32 - Integer.numberOfLeadingZeros(nodesToRelocate - 1));
179             allocateNodeLengthsWithRelocation(array, nodesToRelocate, insertDepth);
180         }
181     }
182 
183     private Bzip2HuffmanAllocator() { }
184 }