Completed
Push — enumset_produce ( 6ef986 )
by Marc
02:00
created

EnumSet::intersect()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 19
Code Lines 12

Duplication

Lines 19
Ratio 100 %

Code Coverage

Tests 9
CRAP Score 3.4098

Importance

Changes 0
Metric Value
c 0
b 0
f 0
dl 19
loc 19
ccs 9
cts 14
cp 0.6429
rs 9.4285
cc 3
eloc 12
nc 3
nop 1
crap 3.4098
1
<?php
2
3
namespace MabeEnum;
4
5
use Iterator;
6
use Countable;
7
use InvalidArgumentException;
8
9
/**
10
 * EnumSet implementation in base of an integer bit set
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
    private $enumeration;
23
24
    /**
25
     * BitSet of all attached enumerations in little endian
26
     * @var string
27
     */
28
    private $bitset;
29
30
    /**
31
     * Ordinal number of current iterator position
32
     * @var int
33
     */
34
    private $ordinal = 0;
35
36
    /**
37
     * Highest possible ordinal number
38
     * @var int
39
     */
40
    private $ordinalMax;
41
42
    /**
43
     * Constructor
44
     *
45
     * @param string $enumeration The classname of the enumeration
46
     * @throws InvalidArgumentException
47
     */
48 46
    public function __construct($enumeration)
49
    {
50 46 View Code Duplication
        if (!is_subclass_of($enumeration, __NAMESPACE__ . '\Enum')) {
51 1
            throw new InvalidArgumentException(sprintf(
52 1
                "This EnumSet can handle subclasses of '%s' only",
53
                __NAMESPACE__ . '\Enum'
54 1
            ));
55
        }
56
        
57 45
        $this->enumeration = $enumeration;
58 45
        $this->ordinalMax  = count($enumeration::getConstants());
59
        
60
        // init the bitset with zeros
61 45
        $this->bitset = str_repeat("\0", ceil($this->ordinalMax / 8));
62 45
    }
63
64
    /**
65
     * Get the classname of enumeration this set is for
66
     * @return string
67
     * @deprecated Please use getEnumeration() instead
68
     */
69
    public function getEnumClass()
70
    {
71
        return $this->getEnumeration();
72
    }
73
74
    /**
75
     * Get the classname of the enumeration
76
     * @return string
77
     */
78 12
    public function getEnumeration()
79
    {
80 12
        return $this->enumeration;
81
    }
82
83
    /**
84
     * Attach a new enumerator or overwrite an existing one
85
     * @param Enum|null|boolean|int|float|string $enumerator
86
     * @return void
87
     * @throws InvalidArgumentException On an invalid given enumerator
88
     */
89 35
    public function attach($enumerator)
90
    {
91 35
        $enumeration = $this->enumeration;
92 35
        $this->setBit($enumeration::get($enumerator)->getOrdinal());
93 35
    }
94
95
    /**
96
     * Detach the given enumerator
97
     * @param Enum|null|boolean|int|float|string $enumerator
98
     * @return void
99
     * @throws InvalidArgumentException On an invalid given enumerator
100
     */
101 7
    public function detach($enumerator)
102
    {
103 7
        $enumeration = $this->enumeration;
104 7
        $this->unsetBit($enumeration::get($enumerator)->getOrdinal());
105 7
    }
106
107
    /**
108
     * Test if the given enumerator was attached
109
     * @param Enum|null|boolean|int|float|string $enumerator
110
     * @return boolean
111
     */
112 11
    public function contains($enumerator)
113
    {
114 11
        $enumeration = $this->enumeration;
115 11
        return $this->getBit($enumeration::get($enumerator)->getOrdinal());
116
    }
117
118
    /* Iterator */
119
120
    /**
121
     * Get the current enumerator
122
     * @return Enum|null Returns the current enumerator or NULL on an invalid iterator position
123
     */
124 9
    public function current()
125
    {
126 9
        if ($this->valid()) {
127 9
            $enumeration = $this->enumeration;
128 9
            return $enumeration::getByOrdinal($this->ordinal);
129
        }
130
131 2
        return null;
132
    }
133
134
    /**
135
     * Get the ordinal number of the current iterator position
136
     * @return int
137
     */
138 6
    public function key()
139
    {
140 6
        return $this->ordinal;
141
    }
142
143
    /**
144
     * Go to the next valid iterator position.
145
     * If no valid iterator position is found the iterator position will be the last possible + 1.
146
     * @return void
147
     */
148 16
    public function next()
149
    {
150
        do {
151 16
            if (++$this->ordinal >= $this->ordinalMax) {
152 4
                $this->ordinal = $this->ordinalMax;
153 4
                return;
154
            }
155 16
        } while (!$this->getBit($this->ordinal));
156 16
    }
157
158
    /**
159
     * Go to the first valid iterator position.
160
     * If no valid iterator position in found the iterator position will be 0.
161
     * @return void
162
     */
163 10
    public function rewind()
164
    {
165 10
        if (trim($this->bitset, "\0") !== '') {
166 10
            $this->ordinal = -1;
167 10
            $this->next();
168 10
        } else {
169 1
            $this->ordinal = 0;
170
        }
171 10
    }
172
173
    /**
174
     * Test if the iterator in a valid state
175
     * @return boolean
176
     */
177 10
    public function valid()
178
    {
179 10
        return $this->ordinal !== $this->ordinalMax && $this->getBit($this->ordinal);
180
    }
181
182
    /* Countable */
183
184
    /**
185
     * Count the number of elements
186
     * @return int
187
     */
188 11
    public function count()
189
    {
190 11
        $cnt = 0;
191 11
        $ord = 0;
192
193 11
        while ($ord !== $this->ordinalMax) {
194 10
            if ($this->getBit($ord++)) {
195 10
                ++$cnt;
196 10
            }
197 10
        }
198
199 11
        return $cnt;
200
    }
201
202
    /**
203
     * Check if this EnumSet is the same as other
204
     * @param EnumSet $other
205
     * @return bool
206
     */
207 2
    public function isEqual(EnumSet $other)
208
    {
209 2
        return $this->getEnumeration() === $other->getEnumeration()
210 2
            && $this->getBinaryBitsetLe() === $other->getBinaryBitsetLe();
211
    }
212
213
    /**
214
     * Check if this EnumSet is a subset of other
215
     * @param EnumSet $other
216
     * @return bool
217
     */
218 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...
219
    {
220 4
        if ($this->getEnumeration() !== $other->getEnumeration()) {
221 1
            return false;
222
        }
223
224 3
        $thisBitset = $this->getBinaryBitsetLe();
225 3
        return ($thisBitset & $other->getBinaryBitsetLe()) === $thisBitset;
226
    }
227
228
    /**
229
     * Check if this EnumSet is a superset of other
230
     * @param EnumSet $other
231
     * @return bool
232
     */
233 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...
234
    {
235 4
        if ($this->getEnumeration() !== $other->getEnumeration()) {
236 1
            return false;
237
        }
238
239 3
        $thisBitset = $this->getBinaryBitsetLe();
240 3
        return ($thisBitset | $other->getBinaryBitsetLe()) === $thisBitset;
241
    }
242
243
    /**
244
     * Produce a new set with enumerators from both this and other (this | other)
245
     * @param EnumSet $other,... Other EnumSet(s) of the same enumeration to produce the union
0 ignored issues
show
Bug introduced by
There is no parameter named $other,.... Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
246
     * @return EnumSet
247
     */
248 1 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...
249
    {
250 1
        $bitset = $this->bitset;
251 1
        foreach (func_get_args() as $other) {
252 1
            if ($this->enumeration !== $other->enumeration) {
253
                throw new InvalidArgumentException(sprintf(
254
                    "Others should be an %s of the same enumeration as this '%s'",
255
                    __CLASS__,
256
                    $this->enumeration
257
                ));
258
            }
259
260 1
            $bitset |= $other->bitset;
261 1
        }
262
263 1
        $clone = clone $this;
264 1
        $clone->bitset = $bitset;
265 1
        return $clone;
266
    }
267
268
    /**
269
     * Produce a new set with enumerators common to both this and other (this & other)
270
     * @param EnumSet $other,... Other EnumSet(s) of the same enumeration to produce the union
0 ignored issues
show
Bug introduced by
There is no parameter named $other,.... Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
271
     * @return EnumSet
272
     */
273 1 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...
274
    {
275 1
        $bitset = $this->bitset;
276 1
        foreach (func_get_args() as $other) {
277 1
            if ($this->enumeration !== $other->enumeration) {
278
                throw new InvalidArgumentException(sprintf(
279
                    "Others should be an %s of the same enumeration as this '%s'",
280
                    __CLASS__,
281
                    $this->enumeration
282
                ));
283
            }
284
285 1
            $bitset &= $other->bitset;
286 1
        }
287
288 1
        $clone = clone $this;
289 1
        $clone->bitset = $bitset;
290 1
        return $clone;
291
    }
292
293
    /**
294
     * Produce a new set with enumerators in this but not in other (this - (other | other))
295
     * @param EnumSet $other,... Other EnumSet(s) of the same enumeration to produce the union
0 ignored issues
show
Bug introduced by
There is no parameter named $other,.... Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
296
     * @return EnumSet
297
     */
298 1 View Code Duplication
    public function diff(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...
299
    {
300 1
        $bitset = '';
301 1
        foreach (func_get_args() as $other) {
302 1
            if ($this->enumeration !== $other->enumeration) {
303
                throw new InvalidArgumentException(sprintf(
304
                    "Others should be an %s of the same enumeration as this '%s'",
305
                    __CLASS__,
306
                    $this->enumeration
307
                ));
308
            }
309
310 1
            $bitset |= $other->bitset;
311 1
        }
312
313 1
        $clone = clone $this;
314 1
        $clone->bitset = $this->bitset & ~$bitset;
315 1
        return $clone;
316
    }
317
318
    /**
319
     * Produce a new set with enumerators in either this and other but not in both (this ^ (other | other))
320
     * @param EnumSet $other,... Other EnumSet(s) of the same enumeration to produce the union
0 ignored issues
show
Bug introduced by
There is no parameter named $other,.... Was it maybe removed?

This check looks for PHPDoc comments describing methods or function parameters that do not exist on the corresponding method or function.

Consider the following example. The parameter $italy is not defined by the method finale(...).

/**
 * @param array $germany
 * @param array $island
 * @param array $italy
 */
function finale($germany, $island) {
    return "2:1";
}

The most likely cause is that the parameter was removed, but the annotation was not.

Loading history...
321
     * @return EnumSet
322
     */
323 1 View Code Duplication
    public function symDiff(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...
324
    {
325 1
        $bitset = '';
326 1
        foreach (func_get_args() as $other) {
327 1
            if ($this->enumeration !== $other->enumeration) {
328
                throw new InvalidArgumentException(sprintf(
329
                    "Others should be an %s of the same enumeration as this '%s'",
330
                    __CLASS__,
331
                    $this->enumeration
332
                ));
333
            }
334
335 1
            $bitset |= $other->bitset;
336 1
        }
337
338 1
        $clone = clone $this;
339 1
        $clone->bitset = $this->bitset ^ $bitset;
340 1
        return $clone;
341
    }
342
343
    /**
344
     * Get ordinal numbers of the defined enumerators as array
345
     * @return int[]
346
     */
347 12
    public function getOrdinals()
348
    {
349 12
        $ordinals = array();
350 12
        $byteLen  = strlen($this->bitset);
351
352 12
	for ($bytePos = 0; $bytePos < $byteLen; ++$bytePos) {
353 12
            if ($this->bitset[$bytePos] === "\0") {
354 12
                continue; // fast skip null byte
355
            }
356
357 12
            for ($bitPos = 0; $bitPos < 8; ++$bitPos) {
358 12
                if ((ord($this->bitset[$bytePos]) & (1 << $bitPos)) !== 0) {
359 12
                    $ordinals[] = $bytePos * 8 + $bitPos;
360 12
                }
361 12
            }
362 12
        }
0 ignored issues
show
Coding Style introduced by
Closing brace indented incorrectly; expected 4 spaces, found 8
Loading history...
363
364 12
        return $ordinals;
365
    }
366
367
    /**
368
     * Get values of the defined enumerators as array
369
     * @return null[]|bool[]|int[]|float[]|string[]
370
     */
371 6
    public function getValues()
372
    {
373 6
        $values = array();
374 6
        foreach ($this->getEnumerators() as $enumerator) {
375 6
            $values[] = $enumerator->getValue();
376 6
        }
377 6
        return $values;
378
    }
379
380
    /**
381
     * Get names of the defined enumerators as array
382
     * @return string[]
383
     */
384 2
    public function getNames()
385
    {
386 2
        $names = array();
387 2
        foreach ($this->getEnumerators() as $enumerator) {
388 2
            $names[] = $enumerator->getName();
389 2
        }
390 2
        return $names;
391
    }
392
393
    /**
394
     * Get the defined enumerators as array
395
     * @return Enum[]
396
     */
397 10
    public function getEnumerators()
398
    {
399 10
        $enumeration = $this->enumeration;
400 10
        $enumerators = array();
401 10
        foreach ($this->getOrdinals() as $ord) {
402 10
            $enumerators[] = $enumeration::getByOrdinal($ord);
403 10
        }
404 10
        return $enumerators;
405
    }
406
407
    /**
408
     * Get binary bitset in little-endian order
409
     * 
410
     * @return string
411
     */
412 10
    public function getBinaryBitsetLe()
413
    {
414 10
        return $this->bitset;
415
    }
416
417
    /**
418
     * Set binary bitset in little-endian order
419
     *
420
     * NOTE: It resets the current position of the iterator
421
     * 
422
     * @param string $bitset
423
     * @return void
424
     * @throws InvalidArgumentException On a non string is given as Parameter
425
     */
426 6
    public function setBinaryBitsetLe($bitset)
427
    {
428 6
        if (!is_string($bitset)) {
429 1
            throw new InvalidArgumentException('Bitset must be a string');
430
        }
431
        
432 5
        $size   = ceil($this->ordinalMax / 8);
433 5
        $sizeIn = strlen($bitset);
434
        
435 5
        if ($sizeIn < $size) {
436
            // add "\0" if the given bitset is not long enough
437 1
            $bitset .= str_repeat("\0", $size - $sizeIn);
438 5
        } elseif ($sizeIn > $size) {
439 1
            $bitset = substr($bitset, 0, $size);
440 1
        }
441
        
442 5
        $this->bitset = $bitset;
443
        
444 5
        $this->rewind();
445 5
    }
446
447
    /**
448
     * Get binary bitset in big-endian order
449
     * 
450
     * @return string
451
     */
452 2
    public function getBinaryBitsetBe()
453
    {
454 2
        return strrev($this->bitset);
455
    }
456
457
    /**
458
     * Set binary bitset in big-endian order
459
     *
460
     * NOTE: It resets the current position of the iterator
461
     * 
462
     * @param string $bitset
463
     * @return void
464
     * @throws InvalidArgumentException On a non string is given as Parameter
465
     */
466 3
    public function setBinaryBitsetBe($bitset)
467
    {
468 3
        if (!is_string($bitset)) {
469 1
            throw new InvalidArgumentException('Bitset must be a string');
470
        }
471
        
472 2
	$this->setBinaryBitsetLe(strrev($bitset));
473 2
    }
474
475
    /**
476
     * Get the binary bitset
477
     * 
478
     * @return string Returns the binary bitset in big-endian order
479
     * @deprecated Please use getBinaryBitsetBe() instead
480
     */
481
    public function getBitset()
482
    {
483
        return $this->getBinaryBitsetBe();
484
    }
485
486
    /**
487
     * Set the bitset.
488
     * NOTE: It resets the current position of the iterator
489
     * 
490
     * @param string $bitset The binary bitset in big-endian order
491
     * @return void
492
     * @throws InvalidArgumentException On a non string is given as Parameter
493
     * @deprecated Please use setBinaryBitsetBe() instead
494
     */
495
    public function setBitset($bitset)
496
    {
497
        $this->setBinaryBitsetBE($bitset);
498
    }
499
500
    /**
501
     * Get a bit at the given ordinal number
502
     * 
503
     * @param $ordinal int Ordinal number of bit to get
504
     * @return boolean
505
     */
506 22
    private function getBit($ordinal)
507
    {
508 22
        return (ord($this->bitset[(int) ($ordinal / 8)]) & 1 << ($ordinal % 8)) !== 0;
509
    }
510
511
    /**
512
     * Set a bit at the given ordinal number
513
     * 
514
     * @param $ordinal int Ordnal number of bit to set
515
     * @return void
516
     */
517 35
    private function setBit($ordinal)
518
    {
519 35
        $byte = (int) ($ordinal / 8);
520 35
        $this->bitset[$byte] = $this->bitset[$byte] | chr(1 << ($ordinal % 8));
521 35
    }
522
523
    /**
524
     * Unset a bit at the given ordinal number
525
     * 
526
     * @param $ordinal int Ordinal number of bit to unset
527
     * @return void
528
     */
529 7
    private function unsetBit($ordinal)
530
    {
531 7
        $byte = (int) ($ordinal / 8);
532 7
        $this->bitset[$byte] = $this->bitset[$byte] & chr(~(1 << ($ordinal % 8)));
533 7
    }
534
}
535