Code Coverage
 
Classes and Traits
Functions and Methods
Lines
Total
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
17 / 17
CRAP
100.00% covered (success)
100.00%
62 / 62
NestedGraph
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
17 / 17
33
100.00% covered (success)
100.00%
62 / 62
 __construct
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
7 / 7
 getRootGraph
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getNodes
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 getEdges
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 createNode
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 addNode
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
6 / 6
 getNode
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
3 / 3
 hasNode
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 removeNode
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
8 / 8
 createEdge
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
3 / 3
 addEdge
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
11 / 11
 getEdge
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
3 / 3
 hasEdge
100.00% covered (success)
100.00%
1 / 1
1
100.00% covered (success)
100.00%
1 / 1
 removeEdge
100.00% covered (success)
100.00%
1 / 1
3
100.00% covered (success)
100.00%
8 / 8
 getParentNode
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
1 / 1
 nodeAttrChanged
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
3 / 3
 edgeAttrChanged
100.00% covered (success)
100.00%
1 / 1
2
100.00% covered (success)
100.00%
3 / 3
 onAttrChanged
n/a
0 / 0
1
n/a
0 / 0
<?php declare(strict_types = 1);
/*
 * Copyright (c) 2017, Josef Kufner  <josef@kufner.cz>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *     http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
namespace Smalldb\Graph;
class NestedGraph extends AbstractElement
{
    /**
     * Nodes
     *
     * @var Node[]
     */
    private array $nodes = [];
    /**
     * @var Edge[]
     */
    private array $edges = [];
    /**
     * Node owning this graph if the graph is nested.
     */
    private ?Node $parentNode = null;
    /**
     * Root graph
     */
    protected ?Graph $rootGraph = null;
    /**
     * Node index
     */
    protected ElementAttrIndex $nodeAttrIndex;
    /**
     * Edge index
     */
    protected ElementAttrIndex $edgeAttrIndex;
    /**
     * Constructor.
     */
    protected function __construct(Node $parentNode = null, array $attrs = [])
    {
        parent::__construct($attrs);
        if ($parentNode) {
            $this->parentNode = $parentNode;
            $this->rootGraph = $this->parentNode->getGraph()->getRootGraph();
            $this->nodeAttrIndex = $this->rootGraph->nodeAttrIndex;
            $this->edgeAttrIndex = $this->rootGraph->edgeAttrIndex;
        }
    }
    public function getRootGraph(): Graph
    {
        return $this->rootGraph;
    }
    /**
     * Get all nodes in the graph
     *
     * @return Node[]
     */
    public function getNodes(): array
    {
        return $this->nodes;
    }
    /**
     * Get all edges in the graph
     *
     * @return Edge[]
     */
    public function getEdges(): array
    {
        return $this->edges;
    }
    /**
     * Create node and add it to the graph.
     *
     * @param string $id
     * @param array $attrs
     * @return Node
     */
    public function createNode(string $id, array $attrs = []): Node
    {
        return new Node($this, $id, $attrs);
    }
    /**
     * Add existing node to the graph.
     *
     * @param Node $node
     * @return NestedGraph
     */
    public function addNode(Node $node): self
    {
        $id = $node->getId();
        if (isset($this->nodes[$id])) {
            throw new DuplicateNodeException(sprintf("Node \"%s\" already present in the graph.", $id));
        }
        // Add element
        $this->nodes[$id] = $node;
        // Update attr indexes
        $this->nodeAttrIndex->insertElement($node);
        return $this;
    }
    /**
     * Get node by its ID.
     *
     * @param string $id
     * @return Node
     */
    public function getNode(string $id): Node
    {
        if (isset($this->nodes[$id])) {
            return $this->nodes[$id];
        } else {
            throw new MissingNodeException(sprintf("Node \"%s\" not found in the graph.", $id));
        }
    }
    /**
     * Returns true if a given node exists within the graph.
     */
    public function hasNode(string $id): bool
    {
        return isset($this->nodes[$id]);
    }
    /**
     * Remove node and connected edges from the graph.
     *
     * @param Node $node
     * @return NestedGraph
     */
    public function removeNode(Node $node): self
    {
        $id = $node->getId();
        if (isset($this->nodes[$id])) {
            foreach ($node->getConnectedEdges() as $e) {
                $e->remove();
            }
            unset($this->nodes[$id]);
            $this->nodeAttrIndex->removeElement($node);
            return $this;
        } else {
            throw new MissingNodeException(sprintf("Node \"%s\" not found in the graph.", $id));
        }
    }
    /**
     * Create a new edge and add it to the graph.
     *
     * @param string|null $id ID of the edge, generated automatically if null.
     * @param array $attrs Key-value storage of the edge attributes.
     * @param Node $start
     * @param Node $end
     * @return Edge
     */
    public function createEdge(?string $id, Node $start, Node $end, array $attrs = []): Edge
    {
        if ($id === null) {
            $id = '_' . count($this->getRootGraph()->getAllEdges());
        }
        return new Edge($this, $id, $start, $end, $attrs);
    }
    /**
     * Add existing edge to the graph.
     *
     * @param Edge $edge
     * @return NestedGraph
     */
    public function addEdge(Edge $edge): self
    {
        $id = $edge->getId();
        $start = $edge->getStart();
        $end = $edge->getEnd();
        if (isset($this->rootGraph->edges[$id])) {
            throw new DuplicateEdgeException(sprintf("Edge \"%s\" already present in the graph.", $id));
        }
        // Connect the edge to the nodes
        $start->connectEdge($edge);
        if ($start !== $end) {
            $end->connectEdge($edge);
        }
        // Add element
        $this->edges[$id] = $edge;
        // Update attr indexes
        $this->edgeAttrIndex->insertElement($edge);
        return $this;
    }
    /**
     * Get Edge by its ID.
     *
     * @param string $id
     * @return Edge
     */
    public function getEdge(string $id): Edge
    {
        if (isset($this->edges[$id])) {
            return $this->edges[$id];
        } else {
            throw new MissingEdgeException(sprintf("Edge \"%s\" not found in the graph.", $id));
        }
    }
    /**
     * Returns true if a given edge exists within the graph.
     */
    public function hasEdge(string $id): bool
    {
        return isset($this->edges[$id]);
    }
    /**
     * Remove edge from the graph and disconnect it from nodes.
     *
     * @param Edge $edge
     * @return NestedGraph
     */
    public function removeEdge(Edge $edge): self
    {
        $id = $edge->getId();
        if (isset($this->edges[$id])) {
            if ($this->edges[$id] !== $edge) {
                throw new MissingEdgeException(sprintf("Edge \"%s\" found in the graph is not the expected edge.", $id));  // @codeCoverageIgnore
            }
            $this->edges[$id]->disconnectNodes();
            unset($this->edges[$id]);
            $this->edgeAttrIndex->removeElement($edge);
            return $this;
        } else {
            throw new MissingEdgeException(sprintf("Edge \"%s\" not found in the graph.", $id));
        }
    }
    public function getParentNode(): ?Node
    {
        return $this->parentNode;
    }
    /**
     * @internal
     */
    public function nodeAttrChanged($node, $key, $oldValue, $newValue)
    {
        if ($this->nodeAttrIndex->hasAttrIndex($key)) {
            $this->nodeAttrIndex->update($key, $oldValue, $newValue, $node);
        }
    }
    /**
     * @internal
     */
    public function edgeAttrChanged($node, $key, $oldValue, $newValue)
    {
        if ($this->edgeAttrIndex->hasAttrIndex($key)) {
            $this->edgeAttrIndex->update($key, $oldValue, $newValue, $node);
        }
    }
    /**
     * Handle change of an attribute.
     *
     * @codeCoverageIgnore
     */
    protected function onAttrChanged(string $key, $oldValue, $newValue)
    {
        // No-op.
    }
}