Completed
Push — little_bit_endian_bitset ( fd27b4 )
by Marc
03:00
created

EnumSet::setBinaryBitsetBe()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 8
Code Lines 4

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 5
CRAP Score 2

Importance

Changes 0
Metric Value
dl 0
loc 8
ccs 5
cts 5
cp 1
rs 9.4285
c 0
b 0
f 0
cc 2
eloc 4
nc 2
nop 1
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 20
    public function __construct($enumeration)
49
    {
50 20 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 19
        $this->enumeration = $enumeration;
58 19
        $this->ordinalMax  = count($enumeration::getConstants());
59
        
60
        // init the bitset with zeros
61 19
        $this->bitset = str_repeat("\0", ceil($this->ordinalMax / 8));
62 19
    }
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 12
    public function attach($enumerator)
90
    {
91 12
        $enumeration = $this->enumeration;
92 12
        $this->setBit($enumeration::get($enumerator)->getOrdinal());
93 12
    }
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 6
    public function detach($enumerator)
102
    {
103 6
        $enumeration = $this->enumeration;
104 6
        $this->unsetBit($enumeration::get($enumerator)->getOrdinal());
105 6
    }
106
107
    /**
108
     * Test if the given enumerator was attached
109
     * @param Enum|null|boolean|int|float|string $enumerator
110
     * @return boolean
111
     */
112 9
    public function contains($enumerator)
113
    {
114 9
        $enumeration = $this->enumeration;
115 9
        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 11
    public function next()
149
    {
150
        do {
151 11
            if (++$this->ordinal >= $this->ordinalMax) {
152 4
                $this->ordinal = $this->ordinalMax;
153 4
                return;
154
            }
155 11
        } while (!$this->getBit($this->ordinal));
156 11
    }
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 9
    public function rewind()
164
    {
165 9
        if (trim($this->bitset, "\0") !== '') {
166 9
            $this->ordinal = -1;
167 9
            $this->next();
168 9
        } else {
169 1
            $this->ordinal = 0;
170
        }
171 9
    }
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 8
    public function count()
189
    {
190 8
        $cnt = 0;
191 8
        $ord = 0;
192
        do {
193 8
            if ($this->getBit($ord++)) {
194 8
                ++$cnt;
195 8
            }
196 8
        } while ($ord !== $this->ordinalMax);
197
198 8
        return $cnt;
199
    }
200
201
    /**
202
     * Get binary bitset in little-endian order
203
     * 
204
     * @return string
205
     */
206
    public function getBinaryBitsetLe()
207
    {
208
        return $this->bitset;
209
    }
210
211
    /**
212
     * Set binary bitset in little-endian order
213
     *
214
     * NOTE: It resets the current position of the iterator
215
     * 
216
     * @param string $bitset
217
     * @return void
218
     * @throws InvalidArgumentException On a non string is given as Parameter
219
     */
220 4
    public function setBinaryBitsetLe($bitset)
221
    {
222 4
        if (!is_string($bitset)) {
223
            throw new InvalidArgumentException('Bitset must be a string');
224
        }
225
        
226 4
        $size   = ceil($this->ordinalMax / 8);
227 4
        $sizeIn = strlen($bitset);
228
        
229 4
        if ($sizeIn < $size) {
230
            // add "\0" if the given bitset is not long enough
231 1
            $bitset .= str_repeat("\0", $size - $sizeIn);
232 4
        } elseif ($sizeIn > $size) {
233 1
            $bitset = substr($bitset, 0, $size);
234 1
        }
235
        
236 4
        $this->bitset = $bitset;
237
        
238 4
        $this->rewind();
239 4
    }
240
241
    /**
242
     * Get binary bitset in big-endian order
243
     * 
244
     * @return string
245
     */
246 4
    public function getBinaryBitsetBe()
247
    {
248 4
        return strrev($this->bitset);
249
    }
250
251
    /**
252
     * Set binary bitset in big-endian order
253
     *
254
     * NOTE: It resets the current position of the iterator
255
     * 
256
     * @param string $bitset
257
     * @return void
258
     * @throws InvalidArgumentException On a non string is given as Parameter
259
     */
260 5
    public function setBinaryBitsetBe($bitset)
261
    {
262 5
        if (!is_string($bitset)) {
263 1
            throw new InvalidArgumentException('Bitset must be a string');
264
        }
265
        
266 4
	$this->setBinaryBitsetLe(strrev($bitset));
267 4
    }
268
269
    /**
270
     * Get the binary bitset
271
     * 
272
     * @return string Returns the binary bitset in big-endian order
273
     * @deprecated Please use getBinaryBitsetBE() instead
274
     */
275
    public function getBitset()
276
    {
277
        return $this->getBinaryBitsetBe();
278
    }
279
280
    /**
281
     * Set the bitset.
282
     * NOTE: It resets the current position of the iterator
283
     * 
284
     * @param string $bitset The binary bitset in big-endian order
285
     * @return void
286
     * @throws InvalidArgumentException On a non string is given as Parameter
287
     * @deprecated Please use setBinaryBitsetBE() instead
288
     */
289
    public function setBitset($bitset)
290
    {
291
        $this->setBinaryBitsetBE($bitset);
292
    }
293
294
    /**
295
     * Get a bit at the given ordinal number
296
     * 
297
     * @param $ordinal int Ordinal number of bit to get
298
     * @return boolean
299
     */
300 16
    private function getBit($ordinal)
301
    {
302 16
        return (ord($this->bitset[(int) ($ordinal / 8)]) & 1 << ($ordinal % 8)) !== 0;
303
    }
304
305
    /**
306
     * Set a bit at the given ordinal number
307
     * 
308
     * @param $ordinal int Ordnal number of bit to set
309
     * @return void
310
     */
311 12
    private function setBit($ordinal)
312
    {
313 12
        $byte = (int) ($ordinal / 8);
314 12
        $this->bitset[$byte] = $this->bitset[$byte] | chr(1 << ($ordinal % 8));
315 12
    }
316
317
    /**
318
     * Unset a bit at the given ordinal number
319
     * 
320
     * @param $ordinal int Ordinal number of bit to unset
321
     * @return void
322
     */
323 6
    private function unsetBit($ordinal)
324
    {
325 6
        $byte = (int) ($ordinal / 8);
326 6
        $this->bitset[$byte] = $this->bitset[$byte] & chr(~(1 << ($ordinal % 8)));
327 6
    }
328
}
329