Replace Preconditions.CheckNotNull per RequireNonNull
[bgpcep.git] / util / src / main / java / org / opendaylight / protocol / util / BitArray.java
1 /*
2  * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.protocol.util;
9
10 import static java.util.Objects.requireNonNull;
11
12 import com.google.common.base.Preconditions;
13 import com.google.common.primitives.UnsignedBytes;
14 import io.netty.buffer.ByteBuf;
15
16 /**
17  * This class was created to minimize usage of Java BitSet class, as this one
18  * is hard to use within specifics of network protocols. Uses network byte
19  * order.
20  */
21 public final class BitArray {
22
23     private final int size;
24
25     private final byte[] backingArray;
26
27     private final int offset;
28
29     /**
30      * Creates a BitArray with fixed size of bits. For sizes smaller than
31      * 8 the whole byte is allocated.
32      *
33      * @param size Number of bits relevant for this BitArray. Needs to be
34      * greater than 0.
35      */
36     public BitArray(final int size) {
37         Preconditions.checkArgument(size >= 1, "Minimum size is 1 bit.");
38         this.size = size;
39         this.backingArray = new byte[calculateBytes(size)];
40         this.offset = (calculateBytes(this.size) * Byte.SIZE) - this.size;
41     }
42
43     private BitArray(final byte[] backingArray, final int size) {
44         requireNonNull(backingArray, "Byte Array cannot be null");
45         this.size = size;
46         this.backingArray = (backingArray == null) ? null : backingArray.clone();
47         this.offset = (calculateBytes(this.size) * Byte.SIZE) - this.size;
48     }
49
50     /**
51      * Returns a new BitArray created from bytes from given ByteBuf.
52      *
53      * @param buffer ByteBuf, whose readerIndex will be moved by
54      * minimum number of bytes required for the bit size.
55      * @param size Number of bits to be allocated in BitArray
56      * @return new BitArray
57      */
58     public static BitArray valueOf(final ByteBuf buffer, final int size) {
59         Preconditions.checkArgument(size >= 1, "Minimum size is 1 bit.");
60         requireNonNull(buffer, "Byte Array cannot be null");
61         final byte[] b = new byte[calculateBytes(size)];
62         buffer.readBytes(b, 0, b.length);
63         return new BitArray(b, size);
64     }
65
66     /**
67      * Returns a new BitArray with given byte array as backing
68      * array.
69      *
70      * @param bytes byte array
71      * @return new BitArray
72      */
73     public static BitArray valueOf(final byte[] bytes) {
74         return new BitArray(bytes, bytes.length);
75     }
76
77     /**
78      * Returns new BitArray with given byte as backing
79      * array.
80      *
81      * @param b unsigned byte
82      * @return new BitArray
83      */
84     public static BitArray valueOf(final byte b) {
85         return new BitArray(new byte[] {b}, Byte.SIZE);
86     }
87
88     /**
89      * If the value given is TRUE, sets bit on given position.
90      * Checks for null value. Index is counted from the rightmost
91      * bit as 0 to size -1 being the leftmost bit.
92      *
93      * @param index position of bit that will be set
94      * @param value Boolean
95      */
96     public void set(final int index, final Boolean value) {
97         Preconditions.checkArgument(index < this.size, "Index out of bounds.");
98         if (value == null || value.equals(Boolean.FALSE)) {
99             return;
100         }
101         final int pos = calculatePosition(index);
102         final byte b = this.backingArray[pos];
103         this.backingArray[pos] = (byte) (UnsignedBytes.toInt(b) | mask(index));
104     }
105
106     /**
107      * Returns boolean value for a bit on specific position.
108      * Index is counted from the rightmost bit as 0 to
109      * size -1 being the leftmost bit.
110      *
111      * @param index position of bit
112      * @return boolean value
113      */
114     public boolean get(final int index) {
115         Preconditions.checkArgument(index < this.size, "Index out of bounds.");
116         final byte b = this.backingArray[calculatePosition(index)];
117         return ((byte) (UnsignedBytes.toInt(b) & mask(index))) != 0;
118     }
119
120     /**
121      * Returns the backing byte array of this bitset
122      *
123      * @return byte[]
124      */
125     public byte[] array() {
126         return this.backingArray.clone();
127     }
128
129     /**
130      * If possible, returns one byte as backing array.
131      *
132      * @return byte
133      */
134     public byte toByte() {
135         Preconditions.checkArgument(Byte.SIZE >= this.size, "Cannot put backing array to a single byte.");
136         return this.backingArray[0];
137     }
138
139     /**
140      * Writes backing array to given ByteBuf, even if the backing array is
141      * empty, to preserve the number of allocated bits.
142      *
143      * @param buffer ByteBuf
144      */
145     public void toByteBuf(final ByteBuf buffer) {
146         buffer.writeBytes(this.backingArray);
147     }
148
149     /**
150      * Calculates the size in bytes necessary for given number of bits.
151      *
152      * @param size size
153      * @return minimum byte size to contain the position of the bit
154      */
155     private static int calculateBytes(final int size) {
156         return (size + Byte.SIZE - 1) / Byte.SIZE;
157     }
158
159     /**
160      * Calculates which byte in byte array is going to be affected.
161      *
162      * @param index index of the bit to be changed
163      * @return position in byte array
164      */
165     private int calculatePosition(final int index) {
166         return (index + this.offset) / Byte.SIZE;
167     }
168
169     /**
170      * Returns a byte where only one bit is set
171      *
172      * @param index bit index within full byte array
173      * @return byte with one bit set
174      */
175     private byte mask(final int index) {
176         return (byte) (1 << ((this.size -1 - index) % Byte.SIZE));
177     }
178
179     @Override
180     public String toString() {
181         final StringBuilder b = new StringBuilder("BitArray [");
182         for (int i = 0; i < this.backingArray.length; i++) {
183             b.append(Integer.toBinaryString(UnsignedBytes.toInt(this.backingArray[i])));
184             if (i != this.backingArray.length - 1) {
185                 b.append(' ');
186             }
187         }
188         b.append(']');
189         return b.toString();
190     }
191 }