Completed
Push — master ( 866afb...a71abd )
by Chris
02:43
created

ArrayNode::unshift()   A

Complexity

Conditions 4
Paths 8

Size

Total Lines 18
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4

Importance

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