View Javadoc
1   /*
2    * License : The MIT License
3    * Copyright(c) 2020 Olyutorskii
4    */
5   
6   package io.github.olyutorskii.aletojio;
7   
8   /**
9    * Bit pool.
10   *
11   * <p>Pushing any size of bits to bit-queue tail.
12   *
13   * <p>Chopping any size of bits from bit-queue head.
14   *
15   * <p>From MSB to LSB.
16   */
17  public class BitPool {
18  
19      /**
20       * Default bit size.
21       */
22      public static final int DEFAULT_BITS = 512;
23  
24      private static final long MASK_LONGINT = (1L << Integer.SIZE) - 1L;
25  
26      private static final int BOOLEAN_SIZE = 1;
27      private static final int ELEM_SIZE = Integer.SIZE;
28  
29      private static final int RSFT_DIV_E;
30      private static final int MASK_MOD_E;
31  
32      static {
33          assert Integer.bitCount(ELEM_SIZE) == 1;
34  
35          RSFT_DIV_E = Integer.numberOfTrailingZeros(ELEM_SIZE);
36          MASK_MOD_E = (1 << RSFT_DIV_E) - 1;
37  
38          int testVal = ELEM_SIZE * 3 + (0x5555_5555 & MASK_MOD_E);
39          assert  testVal >> RSFT_DIV_E  == testVal / ELEM_SIZE;
40          assert (testVal &  MASK_MOD_E) == testVal % ELEM_SIZE;
41      }
42  
43      // pushing and chopping bits from MSB to LSB.
44      // pushing and chopping bits from lower to higher array index if not overlapping.
45      private final int[] buf;
46  
47      private final int bufCapa;
48  
49      private int bufSize;
50      private int headRawBitPos;
51      private int tailRawBitPos;
52  
53  
54      /**
55       * Constructor.
56       *
57       * <p>bit size is 512.
58       */
59      public BitPool() {
60          this(DEFAULT_BITS);
61          return;
62      }
63  
64      /**
65       * Constructor.
66       *
67       * @param bitLength bit length
68       * @throws IllegalArgumentException length is not positive value
69       */
70      public BitPool(int bitLength) throws IllegalArgumentException {
71          super();
72  
73          if (bitLength <= 0) throw new IllegalArgumentException();
74  
75          this.bufCapa = bitLength;
76  
77          int bufLen = (this.bufCapa - 1) / ELEM_SIZE + 1;
78          this.buf = new int[bufLen];
79  
80          this.bufSize = 0;
81          this.headRawBitPos = 0;
82          this.tailRawBitPos = 0;
83  
84          return;
85      }
86  
87  
88      /**
89       * Return contiguous bitmask pattern from LSB.
90       *
91       * <p>Result is 0b111 if 3.
92       *
93       * <p>Result is -1 if 32 or larger.
94       *
95       * @param lowerBits mask length
96       * @return bitmask
97       */
98      static int getMaskInt(int lowerBits) {
99          if (lowerBits >= Integer.SIZE) return -1;
100         int result = (1 << lowerBits) - 1;
101         return result;
102     }
103 
104     /**
105      * Return contiguous bitmask pattern from LSB.
106      *
107      * <p>Result is 0b111L if 3.
108      *
109      * <p>Result is -1L if 64 or larger.
110      *
111      * @param lowerBits mask length
112      * @return bitmask
113      */
114     static long getMaskLong(int lowerBits) {
115         if (lowerBits >= Long.SIZE) return -1L;
116         long result = (1L << lowerBits) - 1L;
117         return result;
118     }
119 
120 
121     /**
122      * Resturn bit size.
123      *
124      * @return bit size
125      */
126     public int size() {
127         return this.bufSize;
128     }
129 
130     /**
131      * Return max available size.
132      *
133      * @return capacity size
134      */
135     public int capacity() {
136         return this.bufCapa;
137     }
138 
139     /**
140      * Return true if empty.
141      *
142      * @return true if empty
143      */
144     public boolean isEmpty() {
145         boolean result = this.bufSize <= 0;
146         return result;
147     }
148 
149     /**
150      * Return remaining spaces.
151      *
152      * @return remaining spaces.
153      */
154     public int remaining() {
155         int result = this.bufCapa - this.bufSize;
156         return result;
157     }
158 
159     /**
160      * Return true if has remaining spaces.
161      *
162      * @return true if has remaining spaces
163      */
164     public boolean hasRemaining() {
165         boolean result = this.bufCapa > this.bufSize;
166         return result;
167     }
168 
169     /**
170      * clear bit pool.
171      */
172     public void clear() {
173         this.headRawBitPos = 0;
174         this.tailRawBitPos = 0;
175         this.bufSize = 0;
176         return;
177     }
178 
179     /**
180      * check push bits argument.
181      *
182      * @param bits push bits
183      * @param max bits max
184      * @throws IndexOutOfBoundsException no more space or bits is not positive or over max size
185      */
186     private void checkPushSize(int bits, int max) throws IndexOutOfBoundsException {
187         if (bits <= 0 || remaining() < bits || max < bits) {
188             throw new IndexOutOfBoundsException();
189         }
190         return;
191     }
192 
193     /**
194      * check chop bits argument.
195      *
196      * @param bits chop bits
197      * @param max bits max
198      * @throws IndexOutOfBoundsException not enough bits or bits is not positive or over max size
199      */
200     private void checkChopSize(int bits, int max) throws IndexOutOfBoundsException {
201         if (bits <= 0 || this.bufSize < bits || max < bits) {
202             throw new IndexOutOfBoundsException();
203         }
204         return;
205     }
206 
207     /**
208      * Push boolean value to tail of queue.
209      *
210      * @param bool boolean value
211      * @throws IndexOutOfBoundsException no more space
212      */
213     public void pushBoolean(boolean bool) throws IndexOutOfBoundsException {
214         checkPushSize(BOOLEAN_SIZE, BOOLEAN_SIZE);
215 
216         int bufIndex = this.tailRawBitPos >> RSFT_DIV_E; // / ELEM_SIZE;
217         int bposInInt = this.tailRawBitPos & MASK_MOD_E; // % ELEM_SIZE;
218         int lsft = ELEM_SIZE - 1 - bposInInt;
219 
220         int mask = 1 << lsft;
221 
222         int elem = this.buf[bufIndex];
223         if (bool) {
224             elem |=   mask;
225         } else {
226             elem &=  ~mask;
227         }
228         this.buf[bufIndex] = elem;
229 
230         this.bufSize++;
231         this.tailRawBitPos++;
232         if (this.tailRawBitPos >= this.bufCapa) {
233             this.tailRawBitPos -= this.bufCapa;
234         }
235 
236         return;
237     }
238 
239     /**
240      * Chop boolean value from head of queue.
241      *
242      * @return boolean value
243      * @throws IndexOutOfBoundsException not enough bits
244      */
245     public boolean chopBoolean() throws IndexOutOfBoundsException {
246         checkChopSize(BOOLEAN_SIZE, BOOLEAN_SIZE);
247 
248         int bufIndex = this.headRawBitPos >> RSFT_DIV_E; // / ELEM_SIZE;
249         int bposInInt = this.headRawBitPos & MASK_MOD_E; // % ELEM_SIZE;
250         int lsft = ELEM_SIZE - 1 - bposInInt;
251 
252         int mask = 1 << lsft;
253         int elem = this.buf[bufIndex];
254         boolean result = (elem & mask) != 0;
255 
256         this.bufSize--;
257         this.headRawBitPos++;
258         if (this.headRawBitPos >= this.bufCapa) {
259             this.headRawBitPos -= this.bufCapa;
260         }
261 
262         return result;
263     }
264 
265     /**
266      * Push int value to tail of queue.
267      *
268      * @param iArg int value
269      * @throws IndexOutOfBoundsException no more space
270      */
271     public void pushInt(int iArg) throws IndexOutOfBoundsException {
272         pushInt(iArg, Integer.SIZE);
273         return;
274     }
275 
276     /**
277      * Push lower bits of int value to tail of queue.
278      *
279      * @param iArg int value
280      * @param bitCt bits from LSB
281      * @throws IndexOutOfBoundsException bits is not positive or no more space or over 32
282      */
283     public void pushInt(int iArg, int bitCt) throws IndexOutOfBoundsException {
284         checkPushSize(bitCt, Integer.SIZE);
285 
286         int bits1st = this.bufCapa - this.tailRawBitPos;
287 
288         if (bitCt <= bits1st) {
289             pushIntImpl(iArg, bitCt);
290         } else {
291             // Overlapping end of array
292             int bits2nd = bitCt - bits1st;
293             int highArg = iArg >> bits2nd;
294             pushIntImpl(highArg, bits1st);
295             pushIntImpl(iArg,    bits2nd);
296         }
297 
298         return;
299     }
300 
301     /**
302      * Push lower bits of int value to tail of queue.
303      *
304      * <p>No bits bound argument checking.
305      *
306      * <p>Pushing over last of array elements is not supported.
307      *
308      * @param iArg int value
309      * @param bitCt bits from LSB
310      */
311     private void pushIntImpl(int iArg, int bitCt) {
312         int bposInLastElem = this.tailRawBitPos & MASK_MOD_E; // % ELEM_SIZE;
313         int restInLastElem  = ELEM_SIZE - bposInLastElem;
314 
315         if (restInLastElem < bitCt) {
316             // Overlapping array elements
317             int bits2nd = bitCt - restInLastElem;
318             int highArg = iArg >> bits2nd;
319             pushIntImpl(highArg, restInLastElem);
320             pushIntImpl(iArg,    bits2nd);
321             return;
322         }
323 
324         int bufIndex = this.tailRawBitPos >> RSFT_DIV_E; // / ELEM_SIZE;
325 
326         int gap = restInLastElem - bitCt;
327         int mask = getMaskInt(bitCt) << gap;
328         int bits = (iArg << gap) & mask;
329 
330         int elem = this.buf[bufIndex];
331         elem &= ~mask;
332         elem |=  bits;
333         this.buf[bufIndex] = elem;
334 
335         this.bufSize += bitCt;
336         this.tailRawBitPos += bitCt;
337         if (this.tailRawBitPos >= this.bufCapa) {
338             this.tailRawBitPos -= this.bufCapa;
339         }
340 
341         return;
342     }
343 
344     /**
345      * Chop int value from head of queue.
346      *
347      * @return int value
348      * @throws IndexOutOfBoundsException not enough bits
349      */
350     public int chopInt() throws IndexOutOfBoundsException {
351         return chopInt(Integer.SIZE);
352     }
353 
354     /**
355      * Chop bits and store to lower bits of int value from head of queue.
356      *
357      * @param bitCt bits
358      * @return lower bits filled int value
359      * @throws IndexOutOfBoundsException not enough bits or bits is not positive or over 32
360      */
361     public int chopInt(int bitCt) throws IndexOutOfBoundsException {
362         checkChopSize(bitCt, Integer.SIZE);
363 
364         int result;
365 
366         if (this.headRawBitPos + bitCt <= this.bufCapa) {
367             result = chopIntImpl(bitCt);
368         } else {
369             // Overlapping end of array
370             int bits1st = this.bufCapa - this.headRawBitPos;
371             int bits2nd = bitCt - bits1st;
372             int highVal = chopIntImpl(bits1st);
373             int lowVal  = chopIntImpl(bits2nd);
374             result  = (highVal << bits2nd) | lowVal;
375         }
376 
377         return result;
378     }
379 
380     /**
381      * Chop bits and store to lower bits of int value from head of queue.
382      *
383      * <p>No bits bound argument checking.
384      *
385      * <p>Chopping over last of array elements is not supported.
386      *
387      * @param bitCt bits
388      * @return lower bits filled int value
389      */
390     private int chopIntImpl(int bitCt) {
391         int bposInLastElem = this.headRawBitPos & MASK_MOD_E; // % ELEM_SIZE;
392         int restInLastElem = ELEM_SIZE - bposInLastElem;
393 
394         if (restInLastElem < bitCt) {
395             // Overlapping array elements
396             int bits2nd = bitCt - restInLastElem;
397             int highVal = chopIntImpl(restInLastElem);
398             int lowVal  = chopIntImpl(bits2nd);
399             int result = (highVal << bits2nd) | lowVal;
400             return result;
401         }
402 
403         int bufIndex = this.headRawBitPos >> RSFT_DIV_E; // / ELEM_SIZE;
404 
405         int elem = this.buf[bufIndex];
406         int gap = restInLastElem - bitCt;
407         int result = (elem >> gap) & getMaskInt(bitCt);
408 
409         this.bufSize -= bitCt;
410         this.headRawBitPos += bitCt;
411         if (this.headRawBitPos >= this.bufCapa) {
412             this.headRawBitPos -= this.bufCapa;
413         }
414 
415         return result;
416     }
417 
418     /**
419      * Push long value to tail of queue.
420      *
421      * @param lArg long value
422      * @throws IndexOutOfBoundsException no more space
423      */
424     public void pushLong(long lArg) throws IndexOutOfBoundsException {
425         pushLong(lArg, Long.SIZE);
426         return;
427     }
428 
429     /**
430      * Push lower bits of long value to tail of queue.
431      *
432      * @param lArg long value
433      * @param bitCt bits from LSB
434      * @throws IndexOutOfBoundsException bits is not positive or no more space or over 64
435      */
436     public void pushLong(long lArg, int bitCt) throws IndexOutOfBoundsException {
437         checkPushSize(bitCt, Long.SIZE);
438 
439         if (bitCt <= Integer.SIZE) {
440             pushInt((int) lArg, bitCt);
441             return;
442         }
443 
444         int bits1st = bitCt - Integer.SIZE;
445         int bits2nd = Integer.SIZE;
446 
447         int highArg = (int) (lArg >> Integer.SIZE);
448         int lowArg  = (int) lArg;
449 
450         pushInt(highArg, bits1st);
451         pushInt(lowArg,  bits2nd);
452 
453         return;
454     }
455 
456     /**
457      * Chop long value from head of queue.
458      *
459      * @return long value
460      * @throws IndexOutOfBoundsException not enough bits
461      */
462     public long chopLong() throws IndexOutOfBoundsException {
463         return chopLong(Long.SIZE);
464     }
465 
466     /**
467      * Chop bits and store to lower bits of long value from head of queue.
468      *
469      * @param bitCt bits
470      * @return lower bits filled long value
471      * @throws IndexOutOfBoundsException not enough bits or bits is not positive or over 64
472      */
473     public long chopLong(int bitCt) throws IndexOutOfBoundsException {
474         checkChopSize(bitCt, Long.SIZE);
475 
476         if (bitCt <= Integer.SIZE) {
477             long result = (long) chopInt(bitCt);
478             result &= getMaskLong(bitCt);
479             return result;
480         }
481 
482         int bits1st = bitCt - Integer.SIZE;
483 
484         long highVal = (long) chopInt(bits1st);
485         long lowVal  = (long) chopInt(Integer.SIZE);
486 
487         highVal &= getMaskLong(bits1st);
488         lowVal  &= MASK_LONGINT;
489 
490         long result = (highVal << Integer.SIZE) | lowVal;
491 
492         return result;
493     }
494 
495     /**
496      * Push byte value to tail of queue.
497      *
498      * @param bArg byte value
499      * @throws IndexOutOfBoundsException no more space
500      */
501     public void pushByte(byte bArg) throws IndexOutOfBoundsException {
502         pushInt(bArg, Byte.SIZE);
503         return;
504     }
505 
506     /**
507      * Chop byte value from head of queue.
508      *
509      * @return byte value
510      * @throws IndexOutOfBoundsException not enough bits
511      */
512     public byte chopByte() throws IndexOutOfBoundsException {
513         byte result = (byte) chopInt(Byte.SIZE);
514         return result;
515     }
516 
517 }