BitStringBuilder.java
/*
* Copyright (c) 2025, Stein Eldar Johnsen
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package net.morimekta.collect.util;
import java.util.Arrays;
import static java.lang.Byte.toUnsignedInt;
import static java.util.Objects.requireNonNull;
/**
* A Builder of Bit Strings. The Bit String is a sequence of single bit
* values represented by a boolean array.
*/
public class BitStringBuilder extends BitSequence {
private boolean[] data;
private int size;
public BitStringBuilder() {
super();
data = new boolean[16];
size = 0;
}
public BitStringBuilder(BitSequence bitString) {
super();
requireNonNull(bitString, "bitString == null");
size = bitString.size();
data = new boolean[Math.max(16, size)];
bitString.writeTo(data, 0);
}
public BitStringBuilder(boolean[] bitArray) {
this(bitArray, 0, bitArray.length);
}
public BitStringBuilder(boolean[] bitArray, int offset, int bitCount) {
requireNonNull(bitArray, "bitArray == null");
if (offset < 0 || offset > (bitArray.length - 1)) {
throw new IllegalArgumentException("offset outside [0.." + (bitArray.length - 1) + "], was " + offset);
} else if (bitCount < 0 || bitArray.length < offset + bitCount) {
throw new IllegalArgumentException(
"bitCount outside [0.." +
(bitArray.length - offset) +
"], was " +
bitCount);
}
this.data = new boolean[Math.max(16, bitCount)];
this.size = bitCount;
System.arraycopy(bitArray, offset, data, 0, bitCount);
}
// ---- BitStringBuilder ----
public BitStringBuilder set(int pos, boolean value) {
if (pos < 0 || size < pos) {
throw new IndexOutOfBoundsException(pos + " outside [0.." + size + "]");
}
if (pos == size) {
ensureCapacity(size + 1);
++size;
}
data[pos] = value;
return this;
}
public BitStringBuilder insert(int pos, boolean value) {
if (pos < 0 || size < pos) {
throw new IndexOutOfBoundsException(pos + " outside [0.." + size + "]");
}
ensureCapacity(size + 1);
if (pos < size) {
var cl = size - pos;
System.arraycopy(data, pos, data, pos + 1, cl);
}
data[pos] = value;
++size;
return this;
}
public BitStringBuilder insert(int pos, BitSequence bits) {
return insert(pos, bits.toBitArray());
}
public BitStringBuilder insert(int pos, byte value, int bitCount) {
if (bitCount < 0 || bitCount > 8) {
throw new IllegalArgumentException("Bit count must be between [0..8], was " + bitCount);
}
if (pos == size) {
return append(value, bitCount);
}
ensureCapacity(size + bitCount);
System.arraycopy(data, pos, data, pos + bitCount, size - pos);
writeValue(toUnsignedInt(value), bitCount, pos, data);
size += bitCount;
return this;
}
public BitStringBuilder insert(int pos, int value, int bitCount) {
if (bitCount < 0 || bitCount > 32) {
throw new IllegalArgumentException("Bit count must be between [0..32], was " + bitCount);
}
if (pos == size) {
return append(value, bitCount);
}
ensureCapacity(size + bitCount);
System.arraycopy(data, pos, data, pos + bitCount, size - pos);
writeValue(value, bitCount, pos, data);
size += bitCount;
return this;
}
public BitStringBuilder insert(int pos, boolean... bitArray) {
return insert(pos, bitArray, bitArray.length);
}
public BitStringBuilder insert(int pos, boolean[] bitArray, int bitCount) {
if (pos < 0 || size < pos) {
throw new IndexOutOfBoundsException(pos + " outside [0.." + size + "]");
} else if (bitCount < 0 || bitArray.length < bitCount) {
throw new IllegalArgumentException(
"Invalid bitCount: " + bitCount + " outside [0.." + bitArray.length + "]");
}
if (bitCount == 0) {
return this;
}
ensureCapacity(size + bitCount);
if (pos < size) {
var moveLen = size - pos;
System.arraycopy(data, pos, data, pos + bitCount, moveLen);
}
System.arraycopy(bitArray, 0, data, pos, bitCount);
size += bitCount;
return this;
}
public BitStringBuilder append(boolean value) {
ensureCapacity(size + 1);
data[size] = value;
++size;
return this;
}
public BitStringBuilder append(BitSequence bitString) {
ensureCapacity(size + bitString.size());
bitString.writeTo(data, size);
size += bitString.size();
return this;
}
public BitStringBuilder append(byte value, int bitCount) {
if (bitCount < 1 || bitCount > 8) {
throw new IllegalArgumentException("Bit count must be between [0..8], was " + bitCount);
}
ensureCapacity(size + bitCount);
writeValue(value, bitCount, size, data);
size += bitCount;
return this;
}
public BitStringBuilder append(int value, int bitCount) {
if (bitCount < 1 || bitCount > 32) {
throw new IllegalArgumentException("Bit count must be between [0..32], was " + bitCount);
}
ensureCapacity(size + bitCount);
writeValue(value, bitCount, size, data);
size += bitCount;
return this;
}
public BitStringBuilder append(boolean... bitArray) {
return append(bitArray, bitArray.length);
}
public BitStringBuilder append(boolean[] bitArray, int bitCount) {
if (bitCount < 0 || bitArray.length < bitCount) {
throw new IllegalArgumentException(
"Invalid bitCount: " + bitCount + " outside [0.." + bitArray.length + "]");
} else if (bitCount == 0) {
return this;
}
ensureCapacity(size + bitCount);
System.arraycopy(bitArray, 0, data, size, bitCount);
size += bitCount;
return this;
}
public BitString build() {
if (size == 0) return BitString.EMPTY;
return new BitString(data, 0, size);
}
// ---- BitSequence ----
@Override
public int size() {
return size;
}
@Override
public boolean bitAt(int pos) {
if (pos < 0 || size <= pos) {
throw new IndexOutOfBoundsException(pos + " outside [0.." + size + "]");
}
return data[pos];
}
@Override
public boolean[] toBitArray() {
var out = new boolean[size];
System.arraycopy(data, 0, out, 0, size);
return out;
}
@Override
public byte[] toByteArray() {
return toByteArrayInternal(data, size);
}
@Override
public String toBitString() {
return toStringFromBits(data, size);
}
@Override
protected void writeTo(boolean[] out, int off) {
System.arraycopy(data, 0, out, off, size);
}
// ---- Private ----
private static void writeValue(int value, int bitCount, int offset, boolean[] buf) {
var maskStart = 1 << (bitCount - 1);
for (int i = 0; i < bitCount; i++) {
var mask = maskStart >>> i;
buf[offset + i] = (value & mask) != 0;
}
}
private void ensureCapacity(int minSize) {
if (minSize < data.length) {
return;
}
var newSize = Math.max(minSize, data.length * 2);
data = Arrays.copyOf(data, newSize);
}
}