Completed
Pull Request — master (#59)
by Marc
04:33 queued 02:25
created

EnumSet::getBitset()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 4
ccs 0
cts 0
cp 0
rs 10
c 0
b 0
f 0
cc 1
eloc 2
nc 1
nop 0
crap 2
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 18
    public function __construct($enumeration)
49
    {
50 18 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 17
        $this->enumeration = $enumeration;
58 17
        $this->ordinalMax  = count($enumeration::getConstants());
59
        
60
        // init the bitset with zeros
61 17
        $this->bitset = str_repeat("\0", ceil($this->ordinalMax / 8));
62 17
    }
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 2
    public function getEnumeration()
79
    {
80 2
        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 11
    public function attach($enumerator)
90
    {
91 11
        $enumeration = $this->enumeration;
92 11
        $this->setBit($enumeration::get($enumerator)->getOrdinal());
93 11
    }
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 5
    public function detach($enumerator)
102
    {
103 5
        $enumeration = $this->enumeration;
104 5
        $this->unsetBit($enumeration::get($enumerator)->getOrdinal());
105 5
    }
106
107
    /**
108
     * Test if the given enumerator was attached
109
     * @param Enum|null|boolean|int|float|string $enumerator
110
     * @return boolean
111
     */
112 7
    public function contains($enumerator)
113
    {
114 7
        $enumeration = $this->enumeration;
115 7
        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 5
    public function current()
125
    {
126 5
        if ($this->valid()) {
127 5
            $enumeration = $this->enumeration;
128 5
            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 10
    public function next()
149
    {
150
        do {
151 10
            if (++$this->ordinal >= $this->ordinalMax) {
152 4
                $this->ordinal = $this->ordinalMax;
153 4
                return;
154
            }
155 10
        } while (!$this->getBit($this->ordinal));
156 10
    }
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 8
    public function rewind()
164
    {
165 8
        if (trim($this->bitset, "\0") !== '') {
166 8
            $this->ordinal = -1;
167 8
            $this->next();
168 8
        } else {
169 1
            $this->ordinal = 0;
170
        }
171 8
    }
172
173
    /**
174
     * Test if the iterator in a valid state
175
     * @return boolean
176
     */
177 6
    public function valid()
178
    {
179 6
        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 6
    public function count()
189
    {
190 6
        $cnt = 0;
191 6
        $ord = 0;
192
193 6
        while ($ord !== $this->ordinalMax) {
194 6
            if ($this->getBit($ord++)) {
195 6
                ++$cnt;
196 6
            }
197
        }
198 6
199
        return $cnt;
200
    }
201
202
    /**
203
     * Check if this EnumSet is the same as other
204
     * @param EnumSet $other
205
     * @return bool
206 3
     */
207
    public function isEqual(EnumSet $other)
208 3
    {
209
        return $this->getEnumeration() === $other->getEnumeration()
210
            && $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 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 4
    {
220
        if ($this->getEnumeration() !== $other->getEnumeration()) {
221 4
            return false;
222 1
        }
223
224
        $thisBitset = $this->getBinaryBitsetLe();
225 3
        return ($thisBitset & $other->getBinaryBitsetLe()) === $thisBitset;
226 3
    }
227 3
228
    /**
229 3
     * Check if this EnumSet is a superset of other
230
     * @param EnumSet $other
231 1
     * @return bool
232 3
     */
233 1 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 1
    {
235
        if ($this->getEnumeration() !== $other->getEnumeration()) {
236 3
            return false;
237
        }
238 3
239 3
        $thisBitset = $this->getBinaryBitsetLe();
240
        return ($thisBitset | $other->getBinaryBitsetLe()) === $thisBitset;
241
    }
242
243
    /**
244
     * Get binary bitset in little-endian order
245
     * 
246
     * @return string
247 14
     */
248
    public function getBinaryBitsetLe()
249 14
    {
250
        return $this->bitset;
251
    }
252
253
    /**
254
     * Set binary bitset in little-endian order
255
     *
256
     * NOTE: It resets the current position of the iterator
257
     * 
258 11
     * @param string $bitset
259
     * @return void
260 11
     * @throws InvalidArgumentException On a non string is given as Parameter
261 11
     */
262 11
    public function setBinaryBitsetLe($bitset)
263
    {
264
        if (!is_string($bitset)) {
265
            throw new InvalidArgumentException('Bitset must be a string');
266
        }
267
        
268
        $size   = ceil($this->ordinalMax / 8);
269
        $sizeIn = strlen($bitset);
270 5
        
271
        if ($sizeIn < $size) {
272 5
            // add "\0" if the given bitset is not long enough
273 5
            $bitset .= str_repeat("\0", $size - $sizeIn);
274 5
        } elseif ($sizeIn > $size) {
275
            $bitset = substr($bitset, 0, $size);
276
        }
277
        
278
        $this->bitset = $bitset;
279
        
280
        $this->rewind();
281
    }
282
283
    /**
284
     * Get binary bitset in big-endian order
285
     * 
286
     * @return string
287
     */
288
    public function getBinaryBitsetBe()
289
    {
290
        return strrev($this->bitset);
291
    }
292
293
    /**
294
     * Set binary bitset in big-endian order
295
     *
296
     * NOTE: It resets the current position of the iterator
297
     * 
298
     * @param string $bitset
299
     * @return void
300
     * @throws InvalidArgumentException On a non string is given as Parameter
301
     */
302
    public function setBinaryBitsetBe($bitset)
303
    {
304
        if (!is_string($bitset)) {
305
            throw new InvalidArgumentException('Bitset must be a string');
306
        }
307
        
308
	$this->setBinaryBitsetLe(strrev($bitset));
309
    }
310
311
    /**
312
     * Get the binary bitset
313
     * 
314
     * @return string Returns the binary bitset in big-endian order
315
     * @deprecated Please use getBinaryBitsetBe() instead
316
     */
317
    public function getBitset()
318
    {
319
        return $this->getBinaryBitsetBe();
320
    }
321
322
    /**
323
     * Set the bitset.
324
     * NOTE: It resets the current position of the iterator
325
     * 
326
     * @param string $bitset The binary bitset in big-endian order
327
     * @return void
328
     * @throws InvalidArgumentException On a non string is given as Parameter
329
     * @deprecated Please use setBinaryBitsetBe() instead
330
     */
331
    public function setBitset($bitset)
332
    {
333
        $this->setBinaryBitsetBE($bitset);
334
    }
335
336
    /**
337
     * Get a bit at the given ordinal number
338
     * 
339
     * @param $ordinal int Ordinal number of bit to get
340
     * @return boolean
341
     */
342
    private function getBit($ordinal)
343
    {
344
        return (ord($this->bitset[(int) ($ordinal / 8)]) & 1 << ($ordinal % 8)) !== 0;
345
    }
346
347
    /**
348
     * Set a bit at the given ordinal number
349
     * 
350
     * @param $ordinal int Ordnal number of bit to set
351
     * @return void
352
     */
353
    private function setBit($ordinal)
354
    {
355
        $byte = (int) ($ordinal / 8);
356
        $this->bitset[$byte] = $this->bitset[$byte] | chr(1 << ($ordinal % 8));
357
    }
358
359
    /**
360
     * Unset a bit at the given ordinal number
361
     * 
362
     * @param $ordinal int Ordinal number of bit to unset
363
     * @return void
364
     */
365
    private function unsetBit($ordinal)
366
    {
367
        $byte = (int) ($ordinal / 8);
368
        $this->bitset[$byte] = $this->bitset[$byte] & chr(~(1 << ($ordinal % 8)));
369
    }
370
}
371