ArrayNode::unshift()   A
last analyzed

Complexity

Conditions 4
Paths 8

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4

Importance

Changes 0
Metric Value
eloc 10
dl 0
loc 17
ccs 8
cts 8
cp 1
rs 9.9332
c 0
b 0
f 0
cc 4
nc 8
nop 1
crap 4
1
<?php declare(strict_types=1);
2
3
namespace DaveRandom\Jom;
4
5
use DaveRandom\Jom\Exceptions\EmptySubjectNodeListException;
6
use DaveRandom\Jom\Exceptions\InvalidKeyException;
7
use DaveRandom\Jom\Exceptions\InvalidReferenceNodeException;
8
use DaveRandom\Jom\Exceptions\WriteOperationForbiddenException;
9
use DaveRandom\Jom\Exceptions\InvalidSubjectNodeException;
10
11
final class ArrayNode extends VectorNode
12
{
13 17
    private function incrementKeys(?Node $current, int $amount = 1): void
14
    {
15 17
        while ($current !== null) {
16 14
            $current->key += $amount;
17 14
            $this->children[$current->key] = $current;
18 14
            $current = $current->nextSibling;
19
        }
20
    }
21
22 20
    private function decrementKeys(?Node $current): void
23
    {
24 20
        while ($current !== null) {
25 8
            unset($this->children[$current->key]);
26 8
            $this->children[--$current->key] = $current;
27 8
            $current = $current->nextSibling;
28
        }
29
    }
30
31 27
    private function normalizeIndex($index): int
32
    {
33 27
        if (!\is_int($index) && !\ctype_digit($index)) {
34
            throw new \TypeError('Index must be an integer');
35
        }
36
37 27
        return (int)$index;
38
    }
39
40
    /**
41
     * @param int|string|null Array index or null to push new element
42
     * @throws EmptySubjectNodeListException
43
     * @throws InvalidKeyException
44
     * @throws InvalidReferenceNodeException
45
     * @throws InvalidSubjectNodeException
46
     * @throws WriteOperationForbiddenException
47
     */
48
    private function setNodeAtIndex($index, Node $node): void
49
    {
50
        if ($index === null || $this->normalizeIndex($index) === \count($this->children)) {
51
            $this->push($node);
52
            return;
53
        }
54
55
        if (isset($this->children[$index])) {
56
            $this->replaceNode($node, $this->children[$index]);
57
            return;
58
        }
59
60
        throw new InvalidKeyException("Index '{$index}' is outside the bounds of the array");
61
    }
62
63
    /**
64
     * @param Node[] $nodes
65
     * @throws EmptySubjectNodeListException
66
     */
67 115
    private function assertNodeListNotEmpty(array $nodes): void
68
    {
69 115
        if (empty($nodes)) {
70 2
            throw new EmptySubjectNodeListException("List of nodes to add must contain at least one node");
71
        }
72
    }
73
74
    /**
75
     * @throws InvalidSubjectNodeException
76
     */
77 165
    public function __construct(?array $children = [], ?Document $ownerDocument = null)
78
    {
79 165
        parent::__construct($ownerDocument);
80
81
        try {
82 165
            $i = 0;
83
84 165
            foreach ($children ?? [] as $child) {
85 165
                $this->appendNode($child, $i++);
86
            }
87
        } catch (InvalidSubjectNodeException $e) {
88
            throw $e;
89
        //@codeCoverageIgnoreStart
90
        } catch (\Exception $e) {
91
            throw unexpected($e);
92
        }
93
        //@codeCoverageIgnoreEnd
94
    }
95
96
    /**
97
     * @throws InvalidKeyException
98
     */
99 4
    public function item(int $index): Node
100
    {
101 4
        return $this->offsetGet($index);
102
    }
103
104
    /**
105
     * @throws WriteOperationForbiddenException
106
     * @throws InvalidSubjectNodeException
107
     * @throws EmptySubjectNodeListException
108
     */
109 101
    public function push(Node ...$nodes): void
110
    {
111 101
        $this->assertNodeListNotEmpty($nodes);
112
113 100
        foreach ($nodes as $node) {
114 100
            $this->appendNode($node, \count($this->children));
115
        }
116
    }
117
118
    /**
119
     * @throws WriteOperationForbiddenException
120
     */
121 13
    public function pop(): ?Node
122
    {
123 13
        $node = $this->lastChild;
124
125 13
        if ($node === null) {
126 1
            return null;
127
        }
128
129
        try {
130 13
            $this->remove($node);
131 1
        } catch (WriteOperationForbiddenException $e) {
132 1
            throw $e;
133
        //@codeCoverageIgnoreStart
134
        } catch (\Exception $e) {
135
            throw unexpected($e);
136
        }
137
        //@codeCoverageIgnoreEnd
138
139 12
        return $node;
140
    }
141
142
    /**
143
     * @throws WriteOperationForbiddenException
144
     * @throws InvalidSubjectNodeException
145
     * @throws EmptySubjectNodeListException
146
     */
147 15
    public function unshift(Node ...$nodes): void
148
    {
149 15
        $this->assertNodeListNotEmpty($nodes);
150
151
        try {
152 14
            $beforeNode = $this->firstChild;
153
154 14
            foreach ($nodes as $key => $node) {
155 14
                $this->insertNode($node, $key, $beforeNode);
156
            }
157
158 11
            $this->incrementKeys($beforeNode, \count($nodes));
159 6
        } catch (WriteOperationForbiddenException | InvalidSubjectNodeException $e) {
160 6
            throw $e;
161
        //@codeCoverageIgnoreStart
162
        } catch (\Exception $e) {
163
            throw unexpected($e);
164
        }
165
        //@codeCoverageIgnoreEnd
166
    }
167
168
    /**
169
     * @throws WriteOperationForbiddenException
170
     */
171 9
    public function shift(): ?Node
172
    {
173 9
        $node = $this->firstChild;
174
175 9
        if ($node === null) {
176 1
            return null;
177
        }
178
179
        try {
180 9
            $this->remove($node);
181 1
        } catch (WriteOperationForbiddenException $e) {
182 1
            throw $e;
183
        //@codeCoverageIgnoreStart
184
        } catch (\Exception $e) {
185
            throw unexpected($e);
186
        }
187
        //@codeCoverageIgnoreEnd
188
189 8
        return $node;
190
    }
191
192
    /**
193
     * @throws WriteOperationForbiddenException
194
     * @throws InvalidSubjectNodeException
195
     * @throws InvalidReferenceNodeException
196
     */
197 32
    public function insert(Node $node, ?Node $beforeNode): void
198
    {
199 32
        if ($beforeNode === null) {
200 12
            $this->appendNode($node, \count($this->children));
201 6
            return;
202
        }
203
204 20
        $key = $beforeNode !== null
205 20
            ? $beforeNode->key
206 20
            : \count($this->children);
207
208 20
        $this->insertNode($node, $key, $beforeNode);
209
210 6
        $this->incrementKeys($beforeNode);
211
    }
212
213
    /**
214
     * @param Node|int $nodeOrIndex
215
     * @throws InvalidKeyException
216
     * @throws InvalidReferenceNodeException
217
     * @throws InvalidSubjectNodeException
218
     * @throws WriteOperationForbiddenException
219
     */
220 21
    public function replace(Node $newNode, $nodeOrIndex): void
221
    {
222 21
        $this->replaceNode($newNode, $this->resolveNode($nodeOrIndex));
223
    }
224
225
    /**
226
     * @throws WriteOperationForbiddenException
227
     * @throws InvalidSubjectNodeException
228
     */
229 22
    public function remove(Node $node): void
230
    {
231 22
        $next = $node->nextSibling;
232
233 22
        $this->removeNode($node);
234
235 20
        $this->decrementKeys($next);
236
    }
237
238
    /**
239
     * @param int|string $index
240
     * @throws InvalidKeyException
241
     */
242 27
    public function offsetGet($index): Node
243
    {
244 27
        $index = $this->normalizeIndex($index);
245
246 27
        if (!isset($this->children[$index])) {
247 7
            throw new InvalidKeyException("Index '{$index}' is outside the bounds of the array");
248
        }
249
250 26
        return $this->children[$index];
251
    }
252
253
    /**
254
     * @param int|string $index
255
     * @param Node $value
256
     * @throws WriteOperationForbiddenException
257
     * @throws InvalidSubjectNodeException
258
     * @throws InvalidKeyException
259
     */
260
    public function offsetSet($index, $value): void
261
    {
262
        try {
263
            $this->setNodeAtIndex($index, $value);
264
        } catch (WriteOperationForbiddenException | InvalidSubjectNodeException | InvalidKeyException $e) {
265
            throw $e;
266
        //@codeCoverageIgnoreStart
267
        } catch (\Exception $e) {
268
            throw unexpected($e);
269
        }
270
        //@codeCoverageIgnoreEnd
271
    }
272
273
    public function getValue(): array
274
    {
275
        $result = [];
276
277
        foreach ($this as $value) {
278
            $result[] = $value->getValue();
279
        }
280
281
        return $result;
282
    }
283
284
    public function toArray(): array
285
    {
286
        return $this->getValue();
287
    }
288
}
289