BUG-2794 : created BitArray class 68/15668/9
authorDana Kutenicsova <dkutenic@cisco.com>
Tue, 24 Feb 2015 16:05:37 +0000 (17:05 +0100)
committerDana Kutenicsova <dkutenic@cisco.com>
Thu, 26 Feb 2015 19:10:07 +0000 (20:10 +0100)
Change-Id: I6857fb22eea643d90fb61933fb170b620c6dbf63
Signed-off-by: Dana Kutenicsova <dkutenic@cisco.com>
util/src/main/java/org/opendaylight/protocol/util/BitArray.java [new file with mode: 0644]
util/src/test/java/org/opendaylight/protocol/util/BitArrayTest.java [new file with mode: 0644]

diff --git a/util/src/main/java/org/opendaylight/protocol/util/BitArray.java b/util/src/main/java/org/opendaylight/protocol/util/BitArray.java
new file mode 100644 (file)
index 0000000..347a11e
--- /dev/null
@@ -0,0 +1,186 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.protocol.util;
+
+import com.google.common.base.Preconditions;
+import com.google.common.primitives.UnsignedBytes;
+import io.netty.buffer.ByteBuf;
+
+/**
+ * This class was created to minimize usage of Java BitSet class, as this one
+ * is hard to use within specifics of network protocols. Uses network byte
+ * order.
+ */
+public final class BitArray {
+
+    private final int size;
+
+    private final byte[] backingArray;
+
+    /**
+     * Creates a BitArray with fixed size of bits. For sizes smaller than
+     * 8 the whole byte is allocated.
+     *
+     * @param size Number of bits relevant for this BitArray. Needs to be
+     * greater than 0.
+     */
+    public BitArray(final int size) {
+        Preconditions.checkArgument(size >= 1, "Minimum size is 1 bit.");
+        this.size = size;
+        this.backingArray = new byte[calculateBytes(size)];
+    }
+
+    private BitArray(final byte[] backingArray, final int size) {
+        Preconditions.checkNotNull(backingArray, "Byte Array cannot be null");
+        this.backingArray = backingArray;
+        this.size = size;
+    }
+
+    /**
+     * Returns a new BitArray created from bytes from given ByteBuf.
+     *
+     * @param buffer ByteBuf, whose readerIndex will be moved by
+     * minimum number of bytes required for the bit size.
+     * @param size Number of bits to be allocated in BitArray
+     * @return new BitArray
+     */
+    public static BitArray valueOf(final ByteBuf buffer, final int size) {
+        Preconditions.checkArgument(size >= 1, "Minimum size is 1 bit.");
+        Preconditions.checkNotNull(buffer, "Byte Array cannot be null");
+        final byte[] b = new byte[calculateBytes(size)];
+        buffer.readBytes(b, 0, b.length);
+        return new BitArray(b, size);
+    }
+
+    /**
+     * Returns a new BitArray with given byte array as backing
+     * array.
+     *
+     * @param bytes byte array
+     * @return new BitArray
+     */
+    public static BitArray valueOf(final byte[] bytes) {
+        return new BitArray(bytes, bytes.length);
+    }
+
+    /**
+     * Returns new BitArray with given byte as backing
+     * array.
+     *
+     * @param b unsigned byte
+     * @return new BitArray
+     */
+    public static BitArray valueOf(final byte b) {
+        return new BitArray(new byte[] {b}, Byte.SIZE);
+    }
+
+    /**
+     * Sets bit on given position to the value given as parameter.
+     * Checks for null value. Index is counted from the rightmost
+     * bit as 0 to size -1 being the leftmost bit.
+     *
+     * @param index position of bit that will be set
+     * @param value Boolean
+     */
+    public void set(final int index, final Boolean value) {
+        Preconditions.checkArgument(index < this.size, "Index out of bounds.");
+        if (value == null || value.equals(Boolean.FALSE)) {
+            return;
+        }
+        final int pos = calculatePosition(index);
+        final byte b = this.backingArray[pos];
+        this.backingArray[pos] = (byte) (UnsignedBytes.toInt(b) | mask(index));
+    }
+
+    /**
+     * Returns boolean value for a bit on specific position.
+     * Index is counted from the rightmost bit as 0 to
+     * size -1 being the leftmost bit.
+     *
+     * @param index position of bit
+     * @return boolean value
+     */
+    public boolean get(final int index) {
+        Preconditions.checkArgument(index < this.size, "Index out of bounds.");
+        final byte b = this.backingArray[calculatePosition(index)];
+        return ((byte) (UnsignedBytes.toInt(b) & mask(index))) != 0;
+    }
+
+    /**
+     * Returns the backing byte array of this bitset
+     *
+     * @return byte[]
+     */
+    public byte[] array() {
+        return this.backingArray.clone();
+    }
+
+    /**
+     * If possible, returns one byte as backing array.
+     *
+     * @return byte
+     */
+    public byte toByte() {
+        Preconditions.checkArgument(Byte.SIZE >= this.size, "Cannot put backing array to a single byte.");
+        return this.backingArray[0];
+    }
+
+    /**
+     * Writes backing array to given ByteBuf, even if the backing array is
+     * empty, to preserve the number of allocated bits.
+     *
+     * @param buffer ByteBuf
+     */
+    public void toByteBuf(final ByteBuf buffer) {
+        buffer.writeBytes(this.backingArray);
+    }
+
+    /**
+     * Calculates the size in bytes necessary for given number of bits.
+     *
+     * @param size size
+     * @return minimum byte size to contain the position of the bit
+     */
+    private static int calculateBytes(final int size) {
+        return size/Byte.SIZE + (size % Byte.SIZE == 0 ? 0 : 1);
+    }
+
+    /**
+     * Calculates which byte in byte array is going to be affected.
+     *
+     * @param index index of the bit to be changed
+     * @return position in byte array
+     */
+    private int calculatePosition(final int index) {
+        final int offset = (calculateBytes(this.size) * Byte.SIZE) - this.size;
+        return (index + offset) / Byte.SIZE;
+    }
+
+    /**
+     * Returns a byte where only one bit is set
+     *
+     * @param index bit index within full byte array
+     * @return byte with one bit set
+     */
+    private byte mask(final int index) {
+        return (byte) (1 << ((this.size -1 - index) % Byte.SIZE));
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder b = new StringBuilder("BitArray [");
+        for (int i = 0; i < this.backingArray.length; i++) {
+            b.append(Integer.toBinaryString(UnsignedBytes.toInt(this.backingArray[i])));
+            if (i != this.backingArray.length - 1) {
+                b.append(' ');
+            }
+        }
+        b.append(']');
+        return b.toString();
+    }
+}
diff --git a/util/src/test/java/org/opendaylight/protocol/util/BitArrayTest.java b/util/src/test/java/org/opendaylight/protocol/util/BitArrayTest.java
new file mode 100644 (file)
index 0000000..dfef484
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.protocol.util;
+
+import io.netty.buffer.ByteBuf;
+import io.netty.buffer.Unpooled;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class BitArrayTest {
+
+    @Test
+    public void testCreateBitArray() {
+        Assert.assertArrayEquals(new byte[1], new BitArray(5).array());
+        Assert.assertArrayEquals(new byte[3], new BitArray(23).array());
+        Assert.assertArrayEquals(new byte[3], new BitArray(24).array());
+        Assert.assertArrayEquals(new byte[4], new BitArray(25).array());
+
+        final byte[] a = new byte[] {1, 2, 3, 4};
+        Assert.assertArrayEquals(a, BitArray.valueOf(a).array());
+
+        final byte b = 44;
+        Assert.assertEquals(b, BitArray.valueOf(b).toByte());
+
+        final ByteBuf buf = Unpooled.wrappedBuffer(a);
+        Assert.assertArrayEquals(new byte[] {1, 2}, BitArray.valueOf(buf, 12).array());
+
+        final ByteBuf res = Unpooled.buffer();
+        final BitArray i = BitArray.valueOf(a);
+        i.toByteBuf(res);
+        Assert.assertArrayEquals(new byte[] {1, 2, 3, 4}, ByteArray.readAllBytes(res));
+    }
+
+    @Test
+    public void testSetAndGet() {
+        final BitArray ba = new BitArray(10);
+        ba.set(0, null);
+        ba.set(1, Boolean.TRUE);
+        ba.set(2, Boolean.FALSE);
+        ba.set(3, Boolean.TRUE);
+        ba.set(7, Boolean.TRUE);
+        ba.set(8, Boolean.TRUE);
+        ba.set(9, Boolean.TRUE);
+
+        Assert.assertEquals("BitArray [1 1000111]", ba.toString());
+
+        Assert.assertFalse(ba.get(0));
+        Assert.assertTrue(ba.get(1));
+        Assert.assertFalse(ba.get(2));
+        Assert.assertTrue(ba.get(3));
+        Assert.assertTrue(ba.get(7));
+        Assert.assertTrue(ba.get(8));
+        Assert.assertTrue(ba.get(9));
+    }
+}