Completed
Push — master ( 17c7cd...54374a )
by Chris
02:31
created

ArrayNode::indexIsNextNewElement()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

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