Skip to content

Commit b3db9a3

Browse files
authored
Add binary and base64 serialization to BitSet (#8)
1 parent 13cec60 commit b3db9a3

File tree

2 files changed

+114
-0
lines changed

2 files changed

+114
-0
lines changed

src/Util/BitSet.php

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
namespace DR\JBDiff\Util;
77

8+
use InvalidArgumentException;
89
use Stringable;
910

1011
class BitSet implements Stringable
@@ -83,6 +84,71 @@ public function __unserialize(array $data): void
8384
$this->words = $data;
8485
}
8586

87+
/**
88+
* Convert the BitSet to binary string, which can be turned into BitSet again via @see BitSet::fromBinaryString. Note: this method
89+
* is not compatible between 32-bit and 64-bit systems.
90+
*/
91+
public function toBinaryString(): string
92+
{
93+
if (count($this->words) === 0) {
94+
return '';
95+
}
96+
97+
$words = [];
98+
99+
// ensure all slots between 0 and maxKey have a value
100+
$maxKey = max(...array_keys($this->words));
101+
for ($i = 0; $i <= $maxKey; $i++) {
102+
$words[$i] = $this->words[$i] ?? 0;
103+
}
104+
105+
return pack(PHP_INT_SIZE === 4 ? 'N*' : 'J*', ...$words);
106+
}
107+
108+
/**
109+
* Convert the BitSet to base64 encode binary string, which can be turned into BitSet again via @see BitSet::fromBase64String. Note: this method
110+
* is not compatible between 32-bit and 64-bit systems.
111+
*/
112+
public function toBase64String(): string
113+
{
114+
return base64_encode($this->toBinaryString());
115+
}
116+
117+
public static function fromBinaryString(string $data): BitSet
118+
{
119+
$bitSet = new BitSet();
120+
121+
if ($data === '') {
122+
return $bitSet;
123+
}
124+
125+
$words = unpack(PHP_INT_SIZE === 4 ? 'N*' : 'J*', $data);
126+
if ($words === false || count($words) === 0) {
127+
throw new InvalidArgumentException('Unable to unpack from binary string: ' . base64_encode($data));
128+
}
129+
130+
// cleanup all keys where value = 0;
131+
$index = 0;
132+
foreach ($words as $value) {
133+
if ($value !== 0) {
134+
$bitSet->words[$index] = $value;
135+
}
136+
++$index;
137+
}
138+
139+
return $bitSet;
140+
}
141+
142+
public static function fromBase64String(string $data): BitSet
143+
{
144+
$decoded = base64_decode($data, true);
145+
if ($decoded === false) {
146+
throw new InvalidArgumentException('Unable to decode base64 string: ' . $data);
147+
}
148+
149+
return self::fromBinaryString($decoded);
150+
}
151+
86152
/**
87153
* @return array<int, int>
88154
*/

tests/Unit/Util/BitSetTest.php

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
use AssertionError;
77
use DR\JBDiff\Util\BitSet;
8+
use InvalidArgumentException;
89
use PHPUnit\Framework\Attributes\CoversClass;
910
use PHPUnit\Framework\TestCase;
1011

@@ -129,4 +130,51 @@ public function testSerialize(): void
129130

130131
static::assertEquals($bitSet, $newBitSet);
131132
}
133+
134+
public function testSerializeBinaryString(): void
135+
{
136+
$bitSet = new BitSet();
137+
$bitSet->set(0, 500);
138+
139+
$binaryString = $bitSet->toBinaryString();
140+
$newBitSet = BitSet::fromBinaryString($binaryString);
141+
142+
static::assertEquals($bitSet, $newBitSet);
143+
}
144+
145+
public function testSerializeBinaryStringEmptyBitSet(): void
146+
{
147+
$bitSet = new BitSet();
148+
149+
$binaryString = $bitSet->toBinaryString();
150+
$newBitSet = BitSet::fromBinaryString($binaryString);
151+
152+
static::assertEquals($bitSet, $newBitSet);
153+
}
154+
155+
public function testSerializeBinaryStringUnpackFailure(): void
156+
{
157+
$this->expectException(InvalidArgumentException::class);
158+
$this->expectExceptionMessage('Unable to unpack from binary string');
159+
BitSet::fromBinaryString("\x01\x02");
160+
}
161+
162+
public function testSerializeBase64String(): void
163+
{
164+
$bitSet = new BitSet();
165+
$bitSet->set(5, 6);
166+
$bitSet->set(200, 201);
167+
168+
$base64String = $bitSet->toBase64String();
169+
$newBitSet = BitSet::fromBase64String($base64String);
170+
171+
static::assertEquals($bitSet, $newBitSet);
172+
}
173+
174+
public function testInvalidBase64String(): void
175+
{
176+
$this->expectException(InvalidArgumentException::class);
177+
$this->expectExceptionMessage('Unable to decode base64 string: ##');
178+
BitSet::fromBase64String('##');
179+
}
132180
}

0 commit comments

Comments
 (0)