Passed
Push — master ( 97996b...04a345 )
by Pol
02:40
created

NaryNode::capacity()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
rs 10
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace loophp\phptree\Node;
6
7
use Exception;
8
use loophp\phptree\Traverser\BreadthFirst;
9
use loophp\phptree\Traverser\TraverserInterface;
10
use OutOfBoundsException;
11
12
class NaryNode extends Node implements NaryNodeInterface
13
{
14
    /**
15
     * @var int
16
     */
17
    private $capacity;
18
19
    /**
20
     * @var TraverserInterface
21
     */
22
    private $traverser;
23
24
    /**
25
     * NaryNode constructor.
26
     *
27
     * @param int $capacity
28
     *   The maximum children a node can have. Null for no children,
29
     *   if 0 then any number of children is allowed.
30
     * @param TraverserInterface|null $traverser
31
     *   The traverser.
32
     * @param NodeInterface|null $parent
33
     *   The parent.
34
     */
35 53
    public function __construct(
36
        int $capacity = 0,
37
        ?TraverserInterface $traverser = null,
38
        ?NodeInterface $parent = null
39
    ) {
40 53
        parent::__construct($parent);
41
42 53
        $this->capacity = $capacity;
43
44 53
        $this->traverser = $traverser ?? new BreadthFirst();
45 53
    }
46
47
    /**
48
     * {@inheritdoc}
49
     */
50 35
    public function add(NodeInterface ...$nodes): NodeInterface
51
    {
52 35
        $capacity = $this->capacity();
53
54 35
        if (0 === $capacity) {
55 14
            return parent::add(...$nodes);
56
        }
57
58 23
        foreach ($nodes as $node) {
59 23
            if ($this->degree() < $capacity) {
60 23
                parent::add($node);
61
62 23
                continue;
63
            }
64
65 17
            if (null !== $parent = $this->findFirstAvailableNode($this)) {
66 17
                $parent->add($node);
67
            } else {
68 1
                throw new Exception('Unable to add the node to the tree.');
69
            }
70
        }
71
72 23
        return $this;
73
    }
74
75
    /**
76
     * {@inheritdoc}
77
     */
78 39
    public function capacity(): int
79
    {
80 39
        return $this->capacity;
81
    }
82
83
    /**
84
     * {@inheritdoc}
85
     */
86 20
    public function getTraverser(): TraverserInterface
87
    {
88 20
        return $this->traverser;
89
    }
90
91
    /**
92
     * {@inheritdoc}
93
     */
94 3
    public function offsetSet($offset, $value): void
95
    {
96 3
        if (null === $offset) {
97 2
            $this->add($value);
98
        } else {
99 2
            if (0 !== $this->capacity() && $this->capacity() - 1 < $offset) {
100 1
                throw new OutOfBoundsException('The offset is out of range.');
101
            }
102
103 2
            parent::offsetSet($offset, $value);
104
        }
105 3
    }
106
107
    /**
108
     * Find the first available node in the tree.
109
     *
110
     * When adding nodes to a NaryNode based tree, you must traverse the tree
111
     * and find the first node that can be used as a parent for the node to add.
112
     *
113
     * @param NodeInterface $tree
114
     *   The base node.
115
     *
116
     * @return NodeInterface|null
117
     *   A node, null if none are found.
118
     */
119 17
    protected function findFirstAvailableNode(NodeInterface $tree): ?NodeInterface
120
    {
121 17
        foreach ($this->getTraverser()->traverse($tree) as $candidate) {
122 17
            if (!($candidate instanceof NaryNodeInterface)) {
123 2
                continue;
124
            }
125
126 17
            $capacity = $candidate->capacity();
127
128 17
            if (null === $capacity) {
129
                continue;
130
            }
131
132 17
            if (0 !== $capacity && $candidate->degree() >= $capacity) {
133 17
                continue;
134
            }
135
136 17
            return $candidate;
137
        }
138
139 1
        return null;
140
    }
141
}
142