Completed
Push — feature/74 ( acf369...437aaa )
by Marc
01:33
created

EnumSet::isSuperset()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 8
Ratio 100 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 8
loc 8
ccs 4
cts 4
cp 1
rs 9.4285
cc 2
eloc 4
nc 2
nop 1
crap 2
1
<?php
2
3
namespace MabeEnum;
4
5
use Countable;
6
use Iterator;
7
use InvalidArgumentException;
8
9
/**
10
 * This EnumSet is based on a bitset of a binary string.
11
 *
12
 * @link http://github.com/marc-mabe/php-enum for the canonical source repository
13
 * @copyright Copyright (c) 2015 Marc Bennewitz
14
 * @license http://github.com/marc-mabe/php-enum/blob/master/LICENSE.txt New BSD License
15
 */
16
class EnumSet implements Iterator, Countable
17
{
18
    /**
19
     * The classname of the Enumeration
20
     * @var string
21
     */
22
    protected $enumeration;
23
24
    /**
25
     * Ordinal number of current iterator position
26
     * @var int
27
     */
28
    protected $ordinal = 0;
29
30
    /**
31
     * Highest possible ordinal number
32
     * @var int
33
     */
34
    protected $ordinalMax;
35
36
    /**
37
     * Integer or binary (little endian) bitset used to handle attached enumerations.
38
     * @var int|string
39
     */
40
    private $bitset;
41
42
    /**#@+
43
     * Defined the private method names to be called dependend of
44
     * how the bitset type was set too.
45
     * ... Integer or binary bitset.
46
     * ... *Int or *Bin method
47
     * 
48
     * @var string
49
     */
50
    private $fnDoRewind            = 'doRewindBin';
51
    private $fnDoCount             = 'doCountBin';
52
    private $fnDoGetOrdinals       = 'doGetOrdinalsBin';
53
    private $fnDoGetBit            = 'doGetBitBin';
54
    private $fnDoSetBit            = 'doSetBitBin';
55
    private $fnDoUnsetBit          = 'doUnsetBitBin';
56
    private $fnDoGetBinaryBitsetLe = 'doGetBinaryBitsetLeBin';
57
    private $fnDoSetBinaryBitsetLe = 'doSetBinaryBitsetLeBin';
58
    /**#@-*/
59
60
    /**
61
     * Constructor
62
     *
63
     * @param string $enumeration The classname of the enumeration
64
     * @throws InvalidArgumentException
65
     */
66 49
    public function __construct($enumeration)
67
    {
68 49
        if (!is_subclass_of($enumeration, Enum::class)) {
0 ignored issues
show
Bug introduced by
Due to PHP Bug #53727, is_subclass_of might return inconsistent results on some PHP versions if \MabeEnum\Enum::class can be an interface. If so, you could instead use ReflectionClass::implementsInterface.
Loading history...
69 1
            throw new InvalidArgumentException(sprintf(
70 1
                "%s can handle subclasses of '%s' only",
71 1
                static::class,
72
                Enum::class
73 1
            ));
74
        }
75
76 48
        $this->enumeration = $enumeration;
77 48
        $this->ordinalMax  = count($enumeration::getConstants());
78
79 48
        if ($this->ordinalMax > PHP_INT_SIZE * 8) {
80
            // init the bitset with zeros
81 9
            $this->bitset = str_repeat("\0", ceil($this->ordinalMax / 8));
82 9
        } else  {
0 ignored issues
show
Coding Style introduced by
Expected 1 space after ELSE keyword; 2 found
Loading history...
83 39
            $this->bitset          = 0;
84 39
            $this->fnDoRewind      = 'doRewindInt';
85 39
            $this->fnDoCount       = 'doCountInt';
86 39
            $this->fnDoGetOrdinals = 'doGetOrdinalsInt';
87 39
            $this->fnDoGetBit      = 'doGetBitInt';
88 39
            $this->fnDoSetBit      = 'doSetBitInt';
89 39
            $this->fnDoUnsetBit    = 'doUnsetBitInt';
90 39
            $this->fnDoGetBinaryBitsetLe = 'doGetBinaryBitsetLeInt';
91 39
            $this->fnDoSetBinaryBitsetLe = 'doSetBinaryBitsetLeInt';
92
        }
93 48
    }
94
95
    /**
96
     * Get the classname of the enumeration
97
     * @return string
98
     */
99 1
    public function getEnumeration()
100
    {
101 1
        return $this->enumeration;
102
    }
103
104
    /**
105
     * Attach a new enumerator or overwrite an existing one
106
     * @param Enum|null|boolean|int|float|string $enumerator
107
     * @return void
108
     * @throws InvalidArgumentException On an invalid given enumerator
109
     */
110 34
    public function attach($enumerator)
111
    {
112 34
        $enumeration = $this->enumeration;
113 34
        $this->{$this->fnDoSetBit}($enumeration::get($enumerator)->getOrdinal());
114 34
    }
115
116
    /**
117
     * Detach the given enumerator
118
     * @param Enum|null|boolean|int|float|string $enumerator
119
     * @return void
120
     * @throws InvalidArgumentException On an invalid given enumerator
121
     */
122 6
    public function detach($enumerator)
123
    {
124 6
        $enumeration = $this->enumeration;
125 6
        $this->{$this->fnDoUnsetBit}($enumeration::get($enumerator)->getOrdinal());
126 6
    }
127
128
    /**
129
     * Test if the given enumerator was attached
130
     * @param Enum|null|boolean|int|float|string $enumerator
131
     * @return boolean
132
     */
133 9
    public function contains($enumerator)
134
    {
135 9
        $enumeration = $this->enumeration;
136 9
        return $this->{$this->fnDoGetBit}($enumeration::get($enumerator)->getOrdinal());
137
    }
138
139
    /* Iterator */
140
141
    /**
142
     * Get the current enumerator
143
     * @return Enum|null Returns the current enumerator or NULL on an invalid iterator position
144
     */
145 9
    public function current()
146
    {
147 9
        if ($this->valid()) {
148 9
            $enumeration = $this->enumeration;
149 9
            return $enumeration::byOrdinal($this->ordinal);
150
        }
151
152 2
        return null;
153
    }
154
155
    /**
156
     * Get the ordinal number of the current iterator position
157
     * @return int
158
     */
159 6
    public function key()
160
    {
161 6
        return $this->ordinal;
162
    }
163
164
    /**
165
     * Go to the next valid iterator position.
166
     * If no valid iterator position is found the iterator position will be the last possible + 1.
167
     * @return void
168
     */
169 14
    public function next()
170
    {
171
        do {
172 14
            if (++$this->ordinal >= $this->ordinalMax) {
173 4
                $this->ordinal = $this->ordinalMax;
174 4
                return;
175
            }
176 14
        } while (!$this->{$this->fnDoGetBit}($this->ordinal));
177 14
    }
178
179
    /**
180
     * Go to the first valid iterator position.
181
     * If no valid iterator position was found the iterator position will be 0.
182
     * @return void
183
     * @see doRewindBin
184
     * @see doRewindInt
185
     */
186 8
    public function rewind()
187
    {
188 8
        $this->{$this->fnDoRewind}();
189 8
    }
190
191
    /**
192
     * Go to the first valid iterator position.
193
     * If no valid iterator position was found the iterator position will be 0.
194
     *
195
     * This is the binary bitset implementation.
196
     *
197
     * @return void
198
     * @see rewind
199
     * @see doRewindInt
200
     */
201 3 View Code Duplication
    private function doRewindBin()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
202
    {
203 3
        if (trim($this->bitset, "\0") !== '') {
204 3
            $this->ordinal = -1;
205 3
            $this->next();
206 3
        } else {
207
            $this->ordinal = 0;
208
        }
209 3
    }
210
211
    /**
212
     * Go to the first valid iterator position.
213
     * If no valid iterator position was found the iterator position will be 0.
214
     *
215
     * This is the binary bitset implementation.
216
     *
217
     * @return void
218
     * @see rewind
219
     * @see doRewindBin
220
     */
221 5
    private function doRewindInt()
222
    {
223 5
        if ($this->bitset) {
224 5
            $this->ordinal = -1;
225 5
            $this->next();
226 5
        } else {
227 1
            $this->ordinal = 0;
228
        }
229 5
    }
230
231
    /**
232
     * Test if the iterator in a valid state
233
     * @return boolean
234
     */
235 10
    public function valid()
236
    {
237 10
        return $this->ordinal !== $this->ordinalMax && $this->{$this->fnDoGetBit}($this->ordinal);
238
    }
239
240
    /* Countable */
241
242
    /**
243
     * Count the number of elements
244
     *
245
     * @return int
246
     * @see doCountBin
247
     * @see doCountInt
248
     */
249 9
    public function count()
250
    {
251 9
        return $this->{$this->fnDoCount}();
252
    }
253
254
    /**
255
     * Count the number of elements.
256
     *
257
     * This is the binary bitset implementation.
258
     *
259
     * @return int
260
     * @see count
261
     * @see doCountInt
262
     */
263 4 View Code Duplication
    private function doCountBin()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
264
    {
265 4
        $count = 0;
266 4
        $byteLen = strlen($this->bitset);
267 4
        for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) {
268 4
            if ($this->bitset[$bytePos] === "\0") {
269 4
                continue; // fast skip null byte
270
            }
271
272 4
            for ($bitPos = 0; $bitPos < 8; ++$bitPos) {
273 4
                if ((ord($this->bitset[$bytePos]) & (1 << $bitPos)) !== 0) {
274 4
                    ++$count;
275 4
                }
276 4
            }
277 4
        }
278 4
        return $count;
279
    }
280
281
    /**
282
     * Count the number of elements.
283
     *
284
     * This is the integer bitset implementation.
285
     *
286
     * @return int
287
     * @see count
288
     * @see doCountBin
289
     */
290 5
    private function doCountInt()
291
    {
292 5
        $count = 0;
293 5
        $ord = 0;
294 5
        while ($ord !== $this->ordinalMax) {
295 4
            if ($this->bitset & (1 << $ord++)) {
296 4
                ++$count;
297 4
            }
298 4
        }
299 5
        return $count;
300
    }
301
302
    /**
303
     * Check if this EnumSet is the same as other
304
     * @param EnumSet $other
305
     * @return bool
306
     */
307 3
    public function isEqual(EnumSet $other)
308
    {
309 3
        return $this->enumeration === $other->enumeration
310 3
            && $this->bitset === $other->bitset;
311
    }
312
313
    /**
314
     * Check if this EnumSet is a subset of other
315
     * @param EnumSet $other
316
     * @return bool
317
     */
318 4 View Code Duplication
    public function isSubset(EnumSet $other)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
319
    {
320 4
        if ($this->enumeration !== $other->enumeration) {
321 1
            return false;
322
        }
323
324 3
        return ($this->bitset & $other->bitset) === $this->bitset;
325
    }
326
327
    /**
328
     * Check if this EnumSet is a superset of other
329
     * @param EnumSet $other
330
     * @return bool
331
     */
332 4 View Code Duplication
    public function isSuperset(EnumSet $other)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
333
    {
334 4
        if ($this->enumeration !== $other->enumeration) {
335 1
            return false;
336
        }
337
338 3
        return ($this->bitset | $other->bitset) === $this->bitset;
339
    }
340
341
    /**
342
     * Produce a new set with enumerators from both this and other (this | other)
343
     * @param EnumSet ...$other Other EnumSet(s) of the same enumeration to produce the union
344
     * @return EnumSet
345
     */
346 2 View Code Duplication
    public function union(EnumSet $other)
0 ignored issues
show
Unused Code introduced by
The parameter $other is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
347
    {
348 2
        $bitset = $this->bitset;
349 2
        foreach (func_get_args() as $other) {
350 2
            if (!$other instanceof self || $this->enumeration !== $other->enumeration) {
351 1
                throw new InvalidArgumentException(sprintf(
352 1
                    "Others should be an instance of %s of the same enumeration as this %s",
353 1
                    __CLASS__,
354 1
                    $this->enumeration
355 1
                ));
356
            }
357
358 1
            $bitset |= $other->bitset;
359 1
        }
360
361 1
        $clone = clone $this;
362 1
        $clone->bitset = $bitset;
363 1
        return $clone;
364
    }
365
366
    /**
367
     * Produce a new set with enumerators common to both this and other (this & other)
368
     * @param EnumSet ...$other Other EnumSet(s) of the same enumeration to produce the union
369
     * @return EnumSet
370
     */
371 2 View Code Duplication
    public function intersect(EnumSet $other)
0 ignored issues
show
Unused Code introduced by
The parameter $other is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
372
    {
373 2
        $bitset = $this->bitset;
374 2
        foreach (func_get_args() as $other) {
375 2
            if (!$other instanceof self || $this->enumeration !== $other->enumeration) {
376 1
                throw new InvalidArgumentException(sprintf(
377 1
                    "Others should be an instance of %s of the same enumeration as this %s",
378 1
                    __CLASS__,
379 1
                    $this->enumeration
380 1
                ));
381
            }
382
383 1
            $bitset &= $other->bitset;
384 1
        }
385
386 1
        $clone = clone $this;
387 1
        $clone->bitset = $bitset;
388 1
        return $clone;
389
    }
390
391
    /**
392
     * Produce a new set with enumerators in this but not in other (this - other)
393
     * @param EnumSet ...$other Other EnumSet(s) of the same enumeration to produce the union
394
     * @return EnumSet
395
     */
396 2 View Code Duplication
    public function diff(EnumSet $other)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
397
    {
398 2
        $bitset = $other->bitset;
399 2
        foreach (func_get_args() as $other) {
400 2
            if (!$other instanceof self || $this->enumeration !== $other->enumeration) {
401 1
                throw new InvalidArgumentException(sprintf(
402 1
                    "Others should be an instance of %s of the same enumeration as this %s",
403 1
                    __CLASS__,
404 1
                    $this->enumeration
405 1
                ));
406
            }
407
408 1
            $bitset |= $other->bitset;
409 1
        }
410
411 1
        $clone = clone $this;
412 1
        $clone->bitset = $this->bitset & ~$bitset;
413 1
        return $clone;
414
    }
415
416
    /**
417
     * Produce a new set with enumerators in either this and other but not in both (this ^ (other | other))
418
     * @param EnumSet ...$other Other EnumSet(s) of the same enumeration to produce the union
419
     * @return EnumSet
420
     */
421 2 View Code Duplication
    public function symDiff(EnumSet $other)
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
422
    {
423 2
        $bitset = $other->bitset;
424 2
        foreach (func_get_args() as $other) {
425 2
            if (!$other instanceof self || $this->enumeration !== $other->enumeration) {
426 1
                throw new InvalidArgumentException(sprintf(
427 1
                    "Others should be an instance of %s of the same enumeration as this %s",
428 1
                    __CLASS__,
429 1
                    $this->enumeration
430 1
                ));
431
            }
432
433 1
            $bitset |= $other->bitset;
434 1
        }
435
436 1
        $clone = clone $this;
437 1
        $clone->bitset = $this->bitset ^ $bitset;
438 1
        return $clone;
439
    }
440
441
    /**
442
     * Get ordinal numbers of the defined enumerators as array
443
     * @return int[]
444
     */
445 12
    public function getOrdinals()
446
    {
447 12
        return $this->{$this->fnDoGetOrdinals}();
448
    }
449
450
    /**
451
     * Get ordinal numbers of the defined enumerators as array.
452
     *
453
     * This is the binary bitset implementation.
454
     *
455
     * @return int[]
456
     * @see getOrdinals
457
     * @see goGetOrdinalsInt
458
     */
459 View Code Duplication
    private function doGetOrdinalsBin()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
460
    {
461
        $ordinals = array();
462
        $byteLen = strlen($this->bitset);
463
        for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) {
464
            if ($this->bitset[$bytePos] === "\0") {
465
                continue; // fast skip null byte
466
            }
467
468
            for ($bitPos = 0; $bitPos < 8; ++$bitPos) {
469
                if ((ord($this->bitset[$bytePos]) & (1 << $bitPos)) !== 0) {
470
                    $ordinals[] = $bytePos * 8 + $bitPos;
471
                }
472
            }
473
        }
474
        return $ordinals;
475
    }
476
477
    /**
478
     * Get ordinal numbers of the defined enumerators as array.
479
     *
480
     * This is the integer bitset implementation.
481
     *
482
     * @return int[]
483
     * @see getOrdinals
484
     * @see doGetOrdinalsBin
485
     */
486 12
    private function doGetOrdinalsInt()
487
    {
488 12
        $ordinals = array();
489 12
        for ($ord = 0; $ord < $this->ordinalMax; ++$ord) {
490 12
            if (($this->bitset & (1 << $ord))) {
491 12
                $ordinals[] = $ord;
492 12
            }
493 12
        }
494 12
        return $ordinals;
495
    }
496
497
    /**
498
     * Get values of the defined enumerators as array
499
     * @return null[]|bool[]|int[]|float[]|string[]
500
     */
501 6 View Code Duplication
    public function getValues()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
502
    {
503 6
        $enumeration = $this->enumeration;
504 6
        $values      = array();
505 6
        foreach ($this->getOrdinals() as $ord) {
506 6
            $values[] = $enumeration::byOrdinal($ord)->getValue();
507 6
        }
508 6
        return $values;
509
    }
510
511
    /**
512
     * Get names of the defined enumerators as array
513
     * @return string[]
514
     */
515 2 View Code Duplication
    public function getNames()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
516
    {
517 2
        $enumeration = $this->enumeration;
518 2
        $names       = array();
519 2
        foreach ($this->getOrdinals() as $ord) {
520 2
            $names[] = $enumeration::byOrdinal($ord)->getName();
521 2
        }
522 2
        return $names;
523
    }
524
525
    /**
526
     * Get the defined enumerators as array
527
     * @return Enum[]
528
     */
529 2 View Code Duplication
    public function getEnumerators()
0 ignored issues
show
Duplication introduced by
This method seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
530
    {
531 2
        $enumeration = $this->enumeration;
532 2
        $enumerators = array();
533 2
        foreach ($this->getOrdinals() as $ord) {
534 2
            $enumerators[] = $enumeration::byOrdinal($ord);
535 2
        }
536 2
        return $enumerators;
537
    }
538
539
    /**
540
     * Get binary bitset in little-endian order
541
     * 
542
     * @return string
543
     */
544 3
    public function getBinaryBitsetLe()
545
    {
546 3
        return $this->{$this->fnDoGetBinaryBitsetLe}();
547
    }
548
549
    /**
550
     * Get binary bitset in little-endian order.
551
     *
552
     * This is the binary bitset implementation.
553
     *
554
     * @return string
555
     */
556 3
    private function doGetBinaryBitsetLeBin()
557
    {
558 3
        return $this->bitset;
559
    }
560
561
    /**
562
     * Get binary bitset in little-endian order.
563
     *
564
     * This is the integer bitset implementation.
565
     *
566
     * @return string
567
     */
568
    private function doGetBinaryBitsetLeInt()
569
    {
570
        $bin = pack(PHP_INT_SIZE === 8 ? 'P' : 'V', $this->bitset);
571
        return substr($bin, 0, ceil($this->ordinalMax / 8));
572
    }
573
574
    /**
575
     * Set binary bitset in little-endian order
576
     *
577
     * NOTE: It resets the current position of the iterator
578
     * 
579
     * @param string $bitset
580
     * @return void
581
     * @throws InvalidArgumentException On a non string is given as Parameter
582
     */
583 7
    public function setBinaryBitsetLe($bitset)
584
    {
585 7
        if (!is_string($bitset)) {
586 1
            throw new InvalidArgumentException('Bitset must be a string');
587
        }
588
589 6
        $this->{$this->fnDoSetBinaryBitsetLe}($bitset);
590
591
        // reset the iterator position
592 3
        $this->rewind();
593 3
    }
594
595
    /**
596
     * Set binary bitset in little-endian order
597
     *
598
     * NOTE: It resets the current position of the iterator
599
     *
600
     * @param string $bitset
601
     * @return void
602
     * @throws InvalidArgumentException On a non string is given as Parameter
603
     */
604 4
    private function doSetBinaryBitsetLeBin($bitset)
605
    {
606 4
        $size   = strlen($this->bitset);
607 4
        $sizeIn = strlen($bitset);
608
609 4
        if ($sizeIn < $size) {
610
            // add "\0" if the given bitset is not long enough
611 1
            $bitset .= str_repeat("\0", $size - $sizeIn);
612 4
        } elseif ($sizeIn > $size) {
613
            if (trim(substr($bitset, $size), "\0") !== '') {
614
                throw new InvalidArgumentException('Out-Of-Range bits detected');
615
            }
616
            $bitset = substr($bitset, 0, $size);
617
        }
618
619
        // truncate out-of-range bits of last byte
620 4
        $lastByteMaxOrd = $this->ordinalMax % 8;
621 4
        if ($lastByteMaxOrd !== 0) {
622 4
            $lastByte         = $bitset[$size - 1];
623 4
            $lastByteExpected = chr((1 << $lastByteMaxOrd) - 1) & $lastByte;
624 4
            if ($lastByte !== $lastByteExpected) {
625 1
                throw new InvalidArgumentException('Out-Of-Range bits detected');
626
            }
627
628 3
            $this->bitset = substr($bitset, 0, -1) . $lastByteExpected;
629 3
        }
630
631 3
        $this->bitset = $bitset;
632 3
    }
633
634
    /**
635
     * Set binary bitset in little-endian order
636
     *
637
     * NOTE: It resets the current position of the iterator
638
     *
639
     * @param string $bitset
640
     * @return void
641
     * @throws InvalidArgumentException On a non string is given as Parameter
642
     */
643 2
    private function doSetBinaryBitsetLeInt($bitset)
644
    {
645 2
        $len = strlen($bitset);
646 2
        $int = 0;
647 2
        for ($i = 0; $i < $len; ++$i) {
648 2
            $ord = ord($bitset[$i]);
649
650 2
            if ($ord && $i > PHP_INT_SIZE) {
651
                throw new InvalidArgumentException('Out-Of-Range bits detected');
652
            }
653
654 2
            $int |= $ord << (8 * $i);
655 2
        }
656
657 2
        if ($int & (~0 << $this->ordinalMax)) {
658 2
            throw new InvalidArgumentException('Out-Of-Range bits detected');
659
        }
660
661
        $this->bitset = $int;
662
    }
663
664
    /**
665
     * Get binary bitset in big-endian order
666
     * 
667
     * @return string
668
     */
669 1
    public function getBinaryBitsetBe()
670
    {
671 1
        return strrev($this->bitset);
672
    }
673
674
    /**
675
     * Set binary bitset in big-endian order
676
     *
677
     * NOTE: It resets the current position of the iterator
678
     * 
679
     * @param string $bitset
680
     * @return void
681
     * @throws InvalidArgumentException On a non string is given as Parameter
682
     */
683 2
    public function setBinaryBitsetBe($bitset)
684
    {
685 2
        if (!is_string($bitset)) {
686 1
            throw new InvalidArgumentException('Bitset must be a string');
687
        }
688 1
        $this->setBinaryBitsetLe(strrev($bitset));
689 1
    }
690
691
    /**
692
     * Get a bit at the given ordinal number
693
     *
694
     * @param int $ordinal Ordinal number of bit to get
695
     * @return boolean
696
     */
697
    public function getBit($ordinal)
698
    {
699
        if ($ordinal < 0 || $ordinal > $this->ordinalMax) {
700
            throw new InvalidArgumentException("Ordinal number must be between 0 and {$this->ordinalMax}");
701
        }
702
703
        return $this->{$this->fnDoGetBit}($ordinal);
704
    }
705
706
    /**
707
     * Get a bit at the given ordinal number.
708
     *
709
     * This is the binary bitset implementation.
710
     *
711
     * @param int $ordinal Ordinal number of bit to get
712
     * @return boolean
713
     * @see getBit
714
     * @see doGetBitInt
715
     */
716 6
    private function doGetBitBin($ordinal)
717
    {
718 6
        return (ord($this->bitset[(int) ($ordinal / 8)]) & 1 << ($ordinal % 8)) !== 0;
719
    }
720
721
    /**
722
     * Get a bit at the given ordinal number.
723
     *
724
     * This is the integer bitset implementation.
725
     * 
726
     * @param int $ordinal Ordinal number of bit to get
727
     * @return boolean
728
     * @see getBit
729
     * @see doGetBitBin
730
     */
731 12
    private function doGetBitInt($ordinal)
732
    {
733 12
        return (bool)($this->bitset & (1 << $ordinal));
734
    }
735
736
    /**
737
     * Set a bit at the given ordinal number
738
     *
739
     * @param int $ordinal Ordnal number of bit to set
740
     * @param bool $bit    The bit to set
741
     * @return void
742
     * @see doSetBitBin
743
     * @see doSetBitInt
744
     * @see doUnsetBin
745
     * @see doUnsetInt
746
     */
747
    public function setBit($ordinal, $bit)
748
    {
749
        if ($ordinal < 0 || $ordinal > $this->ordinalMax) {
750
            throw new InvalidArgumentException("Ordinal number must be between 0 and {$this->ordinalMax}");
751
        }
752
753
        if ($bit) {
754
            $this->{$this->fnDoSetBit}($ordinal);
755
        } else {
756
            $this->{$this->fnDoUnsetBit}($ordinal);
757
        }
758
    }
759
760
    /**
761
     * Set a bit at the given ordinal number.
762
     *
763
     * This is the binary bitset implementation.
764
     * 
765
     * @param int $ordinal Ordnal number of bit to set
766
     * @return void
767
     * @see setBit
768
     * @see doSetBitInt
769
     */
770 3
    private function doSetBitBin($ordinal)
771
    {
772 3
        $byte = (int) ($ordinal / 8);
773 3
        $this->bitset[$byte] = $this->bitset[$byte] | chr(1 << ($ordinal % 8));
774 3
    }
775
776
    /**
777
     * Set a bit at the given ordinal number.
778
     *
779
     * This is the binary bitset implementation.
780
     *
781
     * @param int $ordinal Ordnal number of bit to set
782
     * @return void
783
     * @see setBit
784
     * @see doSetBitBin
785
     */
786 31
    private function doSetBitInt($ordinal)
787
    {
788 31
        $this->bitset = $this->bitset | (1 << $ordinal);
789 31
    }
790
791
    /**
792
     * Unset a bit at the given ordinal number.
793
     *
794
     * This is the binary bitset implementation.
795
     *
796
     * @param int $ordinal Ordinal number of bit to unset
797
     * @return void
798
     * @see setBit
799
     * @see doUnsetBitInt
800
     */
801 2
    private function doUnsetBitBin($ordinal)
802
    {
803 2
        $byte = (int) ($ordinal / 8);
804 2
        $this->bitset[$byte] = $this->bitset[$byte] & chr(~(1 << ($ordinal % 8)));
805 2
    }
806
807
    /**
808
     * Unset a bit at the given ordinal number.
809
     *
810
     * This is the integer bitset implementation.
811
     *
812
     * @param int $ordinal Ordinal number of bit to unset
813
     * @return void
814
     * @see setBit
815
     * @see doUnsetBitBin
816
     */
817 4
    private function doUnsetBitInt($ordinal)
818
    {
819 4
        $this->bitset = $this->bitset & ~(1 << $ordinal);
820 4
    }
821
}
822