content: package dk.alexandra.fresco.framework.util;
  
  import java.util.Arrays;
  
  /**
   * This class represents a bit vector. Internally the bit vector is represented
   * by a byte array. This is done to make it easy and fast to carry out internal
   * operations and bit manipulations. However, this also means that an instance
   * MUST contain an amount of bits which is divisible by 8 since a byte always
   * contains 8 bits.
   */
  public class StrictBitVector implements BitVector {
  
    private byte[] bits;
    private final int size;
  
    /**
     * Constructs new strict bit vector, using the byte array given as input for
     * the internal representation. Thus modifying this byte array DIRECTLY
     * modifies the bits in this StrictBitVector object.
     *
     * @param bits
     *          raw bytes
     */
    public StrictBitVector(byte[] bits) {
      this.bits = bits.clone();
      // Note that the amount of bits in the bit vector is the amount of bytes in
      // {@code bits} multiplied
      this.size = bits.length * 8;
    }
  
    /**
     * Creates a StrictBitVector with all entries set to zero.
     *
     * @param size bit size of the vector
     */
    public StrictBitVector(int size) {
      if (size % 8 != 0) {
        throw new IllegalArgumentException("Size must be multiple of 8");
      }
      this.size = size;
      this.bits = new byte[size / 8];
    }
  
    /**
     * Constructs new strict bit vector from a secure source of randomness.
     *
     * @param size the bit size of the vector
     * @param rand secure source of randomness
     */
    public StrictBitVector(int size, Drbg rand) {
      this(size);
      rand.nextBytes(bits);
    }
  
    /**
     * Returns the but at a given index of this vector.
     *
     * @param index
     *          The index of the bit, counting from 0
     * @param isBigEndian
     *          Indicates whether the underlying byte array should be interpreted
     *          as big-endian or little-endian
     * @return Returns the bit at the given index
     */
    public boolean getBit(int index, boolean isBigEndian) {
      rangeCheck(index);
      int actualIndex = isBigEndian ? size - 1 - index : index;
      return ByteArrayHelper.getBit(bits, actualIndex);
    }
  
    @Override
    public boolean getBit(int bit) {
      return getBit(bit, true);
    }
  
    /**
     * Sets the bit at index to value, reading from left-to-right.
     *
     * @param index
     *          index of the bit to be set
     * @param value
     *          value to set bit to
     * @param isBigEndian
     *          Indicates whether the underlying byte array should be interpreted
     *          as big-endian or little-endian
     */
    public void setBit(int index, boolean value, boolean isBigEndian) {
      rangeCheck(index);
      int actualIndex = isBigEndian ? size - 1 - index : index;
      ByteArrayHelper.setBit(bits, actualIndex, value);
    }
  
    @Override
    public void setBit(int index, boolean value) {
      setBit(index, value, true);
    }
  
    @Override
    public int getSize() {
      return size;
    }
  
    /**
     * Returns a reference to the internal byte array representing the bit vector.
     * Thus modifying this  byte array DIRECTLY modifies the bits in this
     * StrictBitVector object.
     * <p>
     * The representation is big-endian, that is the first 8 bits will be in the
     * last byte.
     * </p>
     *
     * @return A byte array with the content of this bit vector
     */
    @Override
    public byte[] toByteArray() {
      return bits;
    }
  
    /**
     * Generates a string representation of this vector.
     *
     * @return A string with the binary representation of this vector
     */
    public String asBinaryString() {
      String binStr = "";
      for (int b = 0; b < size; b++) {
        binStr += getBit(b) ? "1" : "0";
      }
      return binStr;
    }
  
    @Override
    public void xor(BitVector other) {
      ByteArrayHelper.xor(bits, other.toByteArray());
    }
  
    @Override
    public String toString() {
      return "StrictBitVector [bits=" + Arrays.toString(bits) + "]";
    }
  
    @Override
    public int hashCode() {
      final int prime = 31;
      int result = 1;
      result = prime * result + Arrays.hashCode(bits);
      // no need to include size parameter since it is derived directly from bits
      return result;
    }
  
    @Override
    public boolean equals(Object obj) {
      if (this == obj) {
        return true;
      }
      if (obj == null) {
        return false;
      }
      if (getClass() != obj.getClass()) {
        return false;
      }
      StrictBitVector other = (StrictBitVector) obj;
      if (!Arrays.equals(bits, other.bits)) {
        return false;
      }
      // no need to check size parameter since it is derived directly from bits
      return true;
    }
  
    /**
     * Constructs a new StrictBitVector which is the concatenation of the vectors given as input.
     *
     * @param bitVectors
     *          the vectors to concatenate
     * @return the concatenated vector
     */
    public static StrictBitVector concat(StrictBitVector... bitVectors) {
      // compute length of result byte array and number of bits
      int combinedBitLength = 0;
      for (StrictBitVector bitVector : bitVectors) {
        combinedBitLength += bitVector.getSize();
      }
      byte[] combined = new byte[combinedBitLength / 8];
      int offset = 0;
      for (StrictBitVector bitVector : bitVectors) {
        byte[] rawBytes = bitVector.toByteArray();
        System.arraycopy(rawBytes, 0, combined, offset, rawBytes.length);
        offset += rawBytes.length;
      }
      return new StrictBitVector(combined);
    }
  
    private void rangeCheck(int bit) {
      if (bit < 0 || bit >= this.size) {
        throw new IndexOutOfBoundsException("Index out of bounds");
      }
    }
  }
  