Completed
Push — feature/74 ( acf369 )
by Marc
01:25
created

BinaryEnumSet::setBinaryBitsetLe()   C

Complexity

Conditions 7
Paths 11

Size

Total Lines 36
Code Lines 20

Duplication

Lines 36
Ratio 100 %

Code Coverage

Tests 0
CRAP Score 56

Importance

Changes 0
Metric Value
dl 36
loc 36
ccs 0
cts 27
cp 0
rs 6.7272
c 0
b 0
f 0
cc 7
eloc 20
nc 11
nop 1
crap 56
1
<?php
2
3
namespace MabeEnum;
4
5
use Iterator;
6
use Countable;
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 BinaryEnumSet extends AbstractEnumSet
17
{
18
    /**
19
     * Binary BitSet of all attached enumerations in little endian
20
     * @var string
21
     */
22
    private $bitset;
23
24
    /**
25
     * Constructor
26
     *
27
     * @param string $enumeration The classname of the enumeration
28
     * @throws InvalidArgumentException
29
     */
30
    public function __construct($enumeration)
31
    {
32
        parent::__construct($enumeration);
33
34
        // init the bitset with zeros
35
        $this->bitset = str_repeat("\0", ceil($this->ordinalMax / 8));
36
    }
37
38
    /**
39
     * Get the classname of the enumeration
40
     * @return string
41
     */
42
    public function getEnumeration()
43
    {
44
        return $this->enumeration;
45
    }
46
47
    /**
48
     * Attach a new enumerator or overwrite an existing one
49
     * @param Enum|null|boolean|int|float|string $enumerator
50
     * @return void
51
     * @throws InvalidArgumentException On an invalid given enumerator
52
     */
53
    public function attach($enumerator)
54
    {
55
        $enumeration = $this->enumeration;
56
        $this->setBit($enumeration::get($enumerator)->getOrdinal());
57
    }
58
59
    /**
60
     * Detach the given enumerator
61
     * @param Enum|null|boolean|int|float|string $enumerator
62
     * @return void
63
     * @throws InvalidArgumentException On an invalid given enumerator
64
     */
65
    public function detach($enumerator)
66
    {
67
        $enumeration = $this->enumeration;
68
        $this->unsetBit($enumeration::get($enumerator)->getOrdinal());
69
    }
70
71
    /**
72
     * Test if the given enumerator was attached
73
     * @param Enum|null|boolean|int|float|string $enumerator
74
     * @return boolean
75
     */
76
    public function contains($enumerator)
77
    {
78
        $enumeration = $this->enumeration;
79
        return $this->getBit($enumeration::get($enumerator)->getOrdinal());
80
    }
81
82
    /* Iterator */
83
84
    /**
85
     * Get the current enumerator
86
     * @return Enum|null Returns the current enumerator or NULL on an invalid iterator position
87
     */
88
    public function current()
89
    {
90
        if ($this->valid()) {
91
            $enumeration = $this->enumeration;
92
            return $enumeration::byOrdinal($this->ordinal);
93
        }
94
95
        return null;
96
    }
97
98
    /**
99
     * Get the ordinal number of the current iterator position
100
     * @return int
101
     */
102
    public function key()
103
    {
104
        return $this->ordinal;
105
    }
106
107
    /**
108
     * Go to the next valid iterator position.
109
     * If no valid iterator position is found the iterator position will be the last possible + 1.
110
     * @return void
111
     */
112 View Code Duplication
    public function next()
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...
113
    {
114
        do {
115
            if (++$this->ordinal >= $this->ordinalMax) {
116
                $this->ordinal = $this->ordinalMax;
117
                return;
118
            }
119
        } while (!$this->getBit($this->ordinal));
120
    }
121
122
    /**
123
     * Go to the first valid iterator position.
124
     * If no valid iterator position in found the iterator position will be 0.
125
     * @return void
126
     */
127 View Code Duplication
    public function rewind()
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...
128
    {
129
        if (trim($this->bitset, "\0") !== '') {
130
            $this->ordinal = -1;
131
            $this->next();
132
        } else {
133
            $this->ordinal = 0;
134
        }
135
    }
136
137
    /**
138
     * Test if the iterator in a valid state
139
     * @return boolean
140
     */
141
    public function valid()
142
    {
143
        return $this->ordinal !== $this->ordinalMax && $this->getBit($this->ordinal);
144
    }
145
146
    /* Countable */
147
148
    /**
149
     * Count the number of elements
150
     * @return int
151
     */
152 View Code Duplication
    public function count()
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...
153
    {
154
        $count   = 0;
155
        $byteLen = strlen($this->bitset);
156
        for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) {
157
            if ($this->bitset[$bytePos] === "\0") {
158
                continue; // fast skip null byte
159
            }
160
161
            for ($bitPos = 0; $bitPos < 8; ++$bitPos) {
162
                if ((ord($this->bitset[$bytePos]) & (1 << $bitPos)) !== 0) {
163
                    ++$count;
164
                }
165
            }
166
        }
167
168
        return $count;
169
    }
170
171
    /**
172
     * Check if this EnumSet is the same as other
173
     * @param EnumSet $other
174
     * @return bool
175
     */
176
    public function isEqual(EnumSet $other)
177
    {
178
        return $this->enumeration === $other->enumeration
0 ignored issues
show
Bug introduced by
The property enumeration cannot be accessed from this context as it is declared protected in class MabeEnum\EnumSet.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
179
            && $this->bitset === $other->bitset;
0 ignored issues
show
Bug introduced by
The property bitset cannot be accessed from this context as it is declared private in class MabeEnum\EnumSet.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
180
    }
181
182
    /**
183
     * Check if this EnumSet is a subset of other
184
     * @param EnumSet $other
185
     * @return bool
186
     */
187 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...
188
    {
189
        if ($this->enumeration !== $other->enumeration) {
0 ignored issues
show
Bug introduced by
The property enumeration cannot be accessed from this context as it is declared protected in class MabeEnum\EnumSet.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
190
            return false;
191
        }
192
193
        return ($this->bitset & $other->bitset) === $this->bitset;
0 ignored issues
show
Bug introduced by
The property bitset cannot be accessed from this context as it is declared private in class MabeEnum\EnumSet.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
194
    }
195
196
    /**
197
     * Check if this EnumSet is a superset of other
198
     * @param EnumSet $other
199
     * @return bool
200
     */
201 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...
202
    {
203
        if ($this->enumeration !== $other->enumeration) {
0 ignored issues
show
Bug introduced by
The property enumeration cannot be accessed from this context as it is declared protected in class MabeEnum\EnumSet.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
204
            return false;
205
        }
206
207
        return ($this->bitset | $other->bitset) === $this->bitset;
0 ignored issues
show
Bug introduced by
The property bitset cannot be accessed from this context as it is declared private in class MabeEnum\EnumSet.

This check looks for access to properties that are not accessible from the current context.

If you need to make a property accessible to another context you can either raise its visibility level or provide an accessible getter in the defining class.

Loading history...
208
    }
209
210
    /**
211
     * Produce a new set with enumerators from both this and other (this | other)
212
     * @param EnumSet ...$other Other EnumSet(s) of the same enumeration to produce the union
213
     * @return EnumSet
214
     */
215 View Code Duplication
    public function union(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...
216
    {
217
        $bitset = $this->bitset;
218
        foreach (func_get_args() as $other) {
219
            if (!$other instanceof self || $this->enumeration !== $other->enumeration) {
220
                throw new InvalidArgumentException(sprintf(
221
                    "Others should be an instance of %s of the same enumeration as this %s",
222
                    __CLASS__,
223
                    $this->enumeration
224
                ));
225
            }
226
227
            $bitset |= $other->bitset;
228
        }
229
230
        $clone = clone $this;
231
        $clone->bitset = $bitset;
232
        return $clone;
233
    }
234
235
    /**
236
     * Produce a new set with enumerators common to both this and other (this & other)
237
     * @param EnumSet ...$other Other EnumSet(s) of the same enumeration to produce the union
238
     * @return EnumSet
239
     */
240 View Code Duplication
    public function intersect(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...
241
    {
242
        $bitset = $this->bitset;
243
        foreach (func_get_args() as $other) {
244
            if (!$other instanceof self || $this->enumeration !== $other->enumeration) {
245
                throw new InvalidArgumentException(sprintf(
246
                    "Others should be an instance of %s of the same enumeration as this %s",
247
                    __CLASS__,
248
                    $this->enumeration
249
                ));
250
            }
251
252
            $bitset &= $other->bitset;
253
        }
254
255
        $clone = clone $this;
256
        $clone->bitset = $bitset;
257
        return $clone;
258
    }
259
260
    /**
261
     * Produce a new set with enumerators in this but not in other (this - other)
262
     * @param EnumSet ...$other Other EnumSet(s) of the same enumeration to produce the union
263
     * @return EnumSet
264
     */
265 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...
266
    {
267
        $bitset = '';
268
        foreach (func_get_args() as $other) {
269
            if (!$other instanceof self || $this->enumeration !== $other->enumeration) {
270
                throw new InvalidArgumentException(sprintf(
271
                    "Others should be an instance of %s of the same enumeration as this %s",
272
                    __CLASS__,
273
                    $this->enumeration
274
                ));
275
            }
276
277
            $bitset |= $other->bitset;
278
        }
279
280
        $clone = clone $this;
281
        $clone->bitset = $this->bitset & ~$bitset;
282
        return $clone;
283
    }
284
285
    /**
286
     * Produce a new set with enumerators in either this and other but not in both (this ^ (other | other))
287
     * @param EnumSet ...$other Other EnumSet(s) of the same enumeration to produce the union
288
     * @return EnumSet
289
     */
290 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...
291
    {
292
        $bitset = '';
293
        foreach (func_get_args() as $other) {
294
            if (!$other instanceof self || $this->enumeration !== $other->enumeration) {
295
                throw new InvalidArgumentException(sprintf(
296
                    "Others should be an instance of %s of the same enumeration as this %s",
297
                    __CLASS__,
298
                    $this->enumeration
299
                ));
300
            }
301
302
            $bitset |= $other->bitset;
303
        }
304
305
        $clone = clone $this;
306
        $clone->bitset = $this->bitset ^ $bitset;
307
        return $clone;
308
    }
309
310
    /**
311
     * Get ordinal numbers of the defined enumerators as array
312
     * @return int[]
313
     */
314 View Code Duplication
    public function getOrdinals()
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...
315
    {
316
        $ordinals = array();
317
        $byteLen  = strlen($this->bitset);
318
319
        for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) {
320
            if ($this->bitset[$bytePos] === "\0") {
321
                continue; // fast skip null byte
322
            }
323
324
            for ($bitPos = 0; $bitPos < 8; ++$bitPos) {
325
                if ((ord($this->bitset[$bytePos]) & (1 << $bitPos)) !== 0) {
326
                    $ordinals[] = $bytePos * 8 + $bitPos;
327
                }
328
            }
329
        }
330
331
        return $ordinals;
332
    }
333
334
    /**
335
     * Get values of the defined enumerators as array
336
     * @return null[]|bool[]|int[]|float[]|string[]
337
     */
338 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...
339
    {
340
        $enumeration = $this->enumeration;
341
        $values      = array();
342
        foreach ($this->getOrdinals() as $ord) {
343
            $values[] = $enumeration::byOrdinal($ord)->getValue();
344
        }
345
        return $values;
346
    }
347
348
    /**
349
     * Get names of the defined enumerators as array
350
     * @return string[]
351
     */
352 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...
353
    {
354
        $enumeration = $this->enumeration;
355
        $names       = array();
356
        foreach ($this->getOrdinals() as $ord) {
357
            $names[] = $enumeration::byOrdinal($ord)->getName();
358
        }
359
        return $names;
360
    }
361
362
    /**
363
     * Get the defined enumerators as array
364
     * @return Enum[]
365
     */
366 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...
367
    {
368
        $enumeration = $this->enumeration;
369
        $enumerators = array();
370
        foreach ($this->getOrdinals() as $ord) {
371
            $enumerators[] = $enumeration::byOrdinal($ord);
372
        }
373
        return $enumerators;
374
    }
375
376
    /**
377
     * Get binary bitset in little-endian order
378
     * 
379
     * @return string
380
     */
381
    public function getBinaryBitsetLe()
382
    {
383
        return $this->bitset;
384
    }
385
386
    /**
387
     * Set binary bitset in little-endian order
388
     *
389
     * NOTE: It resets the current position of the iterator
390
     * 
391
     * @param string $bitset
392
     * @return void
393
     * @throws InvalidArgumentException On a non string is given as Parameter
394
     */
395 View Code Duplication
    public function setBinaryBitsetLe($bitset)
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...
396
    {
397
        if (!is_string($bitset)) {
398
            throw new InvalidArgumentException('Bitset must be a string');
399
        }
400
401
        $size   = strlen($this->bitset);
402
        $sizeIn = strlen($bitset);
403
404
        if ($sizeIn < $size) {
405
            // add "\0" if the given bitset is not long enough
406
            $bitset .= str_repeat("\0", $size - $sizeIn);
407
        } elseif ($sizeIn > $size) {
408
            if (trim(substr($bitset, $size), "\0") !== '') {
409
                throw new InvalidArgumentException('Out-Of-Range bits detected');
410
            }
411
            $bitset = substr($bitset, 0, $size);
412
        }
413
414
        // truncate out-of-range bits of last byte
415
        $lastByteMaxOrd = $this->ordinalMax % 8;
416
        if ($lastByteMaxOrd !== 0) {
417
            $lastByte         = $bitset[$size - 1];
418
            $lastByteExpected = chr((1 << $lastByteMaxOrd) - 1) & $lastByte;
419
            if ($lastByte !== $lastByteExpected) {
420
                throw new InvalidArgumentException('Out-Of-Range bits detected');
421
            }
422
423
            $this->bitset = substr($bitset, 0, -1) . $lastByteExpected;
424
        }
425
426
        $this->bitset = $bitset;
427
428
        // reset the iterator position
429
        $this->rewind();
430
    }
431
432
    /**
433
     * Get binary bitset in big-endian order
434
     * 
435
     * @return string
436
     */
437
    public function getBinaryBitsetBe()
438
    {
439
        return strrev($this->bitset);
440
    }
441
442
    /**
443
     * Set binary bitset in big-endian order
444
     *
445
     * NOTE: It resets the current position of the iterator
446
     * 
447
     * @param string $bitset
448
     * @return void
449
     * @throws InvalidArgumentException On a non string is given as Parameter
450
     */
451
    public function setBinaryBitsetBe($bitset)
452
    {
453
        if (!is_string($bitset)) {
454
            throw new InvalidArgumentException('Bitset must be a string');
455
        }
456
        $this->setBinaryBitsetLe(strrev($bitset));
457
    }
458
459
    /**
460
     * Get a bit at the given ordinal number
461
     * 
462
     * @param $ordinal int Ordinal number of bit to get
463
     * @return boolean
464
     */
465
    private function getBit($ordinal)
0 ignored issues
show
Bug introduced by
Consider using a different method name as you override a private method of the parent class.

Overwriting private methods is generally fine as long as you also use private visibility. It might still be preferable for understandability to use a different method name.

Loading history...
466
    {
467
        return (ord($this->bitset[(int) ($ordinal / 8)]) & 1 << ($ordinal % 8)) !== 0;
468
    }
469
470
    /**
471
     * Set a bit at the given ordinal number
472
     * 
473
     * @param $ordinal int Ordnal number of bit to set
474
     * @return void
475
     */
476
    private function setBit($ordinal)
0 ignored issues
show
Bug introduced by
Consider using a different method name as you override a private method of the parent class.

Overwriting private methods is generally fine as long as you also use private visibility. It might still be preferable for understandability to use a different method name.

Loading history...
477
    {
478
        $byte = (int) ($ordinal / 8);
479
        $this->bitset[$byte] = $this->bitset[$byte] | chr(1 << ($ordinal % 8));
480
    }
481
482
    /**
483
     * Unset a bit at the given ordinal number
484
     * 
485
     * @param $ordinal int Ordinal number of bit to unset
486
     * @return void
487
     */
488
    private function unsetBit($ordinal)
0 ignored issues
show
Bug introduced by
Consider using a different method name as you override a private method of the parent class.

Overwriting private methods is generally fine as long as you also use private visibility. It might still be preferable for understandability to use a different method name.

Loading history...
489
    {
490
        $byte = (int) ($ordinal / 8);
491
        $this->bitset[$byte] = $this->bitset[$byte] & chr(~(1 << ($ordinal % 8)));
492
    }
493
}
494