blob: db8bba1a2fbfe8c00968bb23ee52636292d6d328 [file] [log] [blame]
<?php
/**
* File containing the ezcTreeMemory class.
*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*
* @license http://www.apache.org/licenses/LICENSE-2.0 Apache License, Version 2.0
* @version //autogentag//
* @filesource
* @package Tree
*/
/**
* ezcTreeMemory is an implementation of a tree backend that operates on
* an in-memory tree structure. Meta-information is kept in objects of the
* ezcTreeMemoryNode class.
*
* Example:
* <code>
* <?php
* // Create a new tree
* $tree = ezcTreeMemory::create( new ezcTreeMemoryDataStore() );
* // or
* $tree = new ezcTreeMemory( new ezcTreeMemoryDataStore() );
* ?>
* </code>
*
* See {@link ezcTree} for examples on how to operate on the tree.
*
* @property-read ezcTreeXmlDataStore $store
* The data store that is used for retrieving/storing data.
* @property string $nodeClassName
* Which class is used as tree node - this class *must* inherit
* the ezcTreeNode class.
*
* @package Tree
* @version //autogentag//
* @mainclass
*/
class ezcTreeMemory extends ezcTree
{
/**
* Contains a list of all nodes, indexed by node ID that link directly to the create node so that they can be looked up quickly.
*
* @var array(string=>ezcTreeMemoryNode)
*/
private $nodeList = array();
/**
* Contains the root node.
*
* @var ezcTreeMemoryNode
*/
private $rootNode;
/**
* Stores the last auto generated ID that was used.
*
* @var integer $autoNodeId
*/
private $autoNodeId = 0;
/**
* Constructs a new ezcTreeMemory object.
*
* The store that is used for data storage should be passed as the
* $store argument.
*
* @param ezcTreeMemoryDataStore $store
*/
protected function __construct( ezcTreeMemoryDataStore $store )
{
$this->properties['store'] = $store;
$this->properties['autoId'] = false;
}
/**
* This method generates the next node ID.
*
* @return integer
*/
protected function generateNodeID()
{
$this->autoNodeId++;
return $this->autoNodeId;
}
/**
* A factory method that creates a new empty tree using the data store $store.
*
* @param ezcTreeMemoryDataStore $store
* @return ezcTreeMemory
*/
public static function create( ezcTreeMemoryDataStore $store )
{
$newTree = new ezcTreeMemory( $store );
$newTree->nodeList = null;
$newTree->rootNode = null;
return $newTree;
}
/**
* Returns whether the node with ID $nodeId exists.
*
* @param string $nodeId
* @return bool
*/
public function nodeExists( $nodeId )
{
return isset( $this->nodeList[$nodeId] );
}
/**
* Returns the node identified by the ID $nodeId.
*
* @param string $nodeId
* @throws ezcTreeInvalidIdException if there is no node with ID $nodeId
* @return ezcTreeNode
*/
public function fetchNodeById( $nodeId )
{
return $this->getNodeById( $nodeId )->node;
}
/**
* Returns the node container for node $nodeId.
*
* @param string $nodeId
* @throws ezcTreeInvalidIdException if there is no node with ID $nodeId
* @return ezcTreeMemoryNode
*/
private function getNodeById( $nodeId )
{
if ( !$this->nodeExists( $nodeId ) )
{
throw new ezcTreeUnknownIdException( $nodeId );
}
return $this->nodeList[$nodeId];
}
/**
* Returns all the children of the node with ID $nodeId.
*
* @param string $nodeId
* @return ezcTreeNodeList
*/
public function fetchChildren( $nodeId )
{
$treeNode = $this->getNodeById( $nodeId );
$list = new ezcTreeNodeList;
foreach ( $treeNode->children as $nodeId => $child )
{
$list->addNode( $child->node );
}
return $list;
}
/**
* Returns the parent node of the node with ID $nodeId.
*
* This method returns null if there is no parent node.
*
* @param string $nodeId
* @return ezcTreeNode
*/
public function fetchParent( $nodeId )
{
$treeNode = $this->getNodeById( $nodeId );
$parentNode = $treeNode->parent;
return $parentNode !== null ? $parentNode->node : null;
}
/**
* Returns all the nodes in the path from the root node to the node with ID
* $nodeId, including those two nodes.
*
* @param string $nodeId
* @return ezcTreeNodeList
*/
public function fetchPath( $nodeId )
{
$list = new ezcTreeNodeList;
$memoryNode = $this->getNodeById( $nodeId );
$nodes = array();
$nodes[] = $memoryNode->node;
$memoryNode = $memoryNode->parent;
while ( $memoryNode !== null )
{
$nodes[] = $memoryNode->node;
$memoryNode = $memoryNode->parent;
}
$list = new ezcTreeNodeList;
foreach ( array_reverse( $nodes ) as $node )
{
$list->addNode( $node );
}
return $list;
}
/**
* Adds the children nodes of the node $memoryNode to the
* ezcTreeNodeList $list.
*
* @param ezcTreeNodeList $list
* @param ezcTreeMemoryNode $memoryNode
*/
private function addChildNodesDepthFirst( ezcTreeNodeList $list, ezcTreeMemoryNode $memoryNode )
{
foreach ( $memoryNode->children as $nodeId => $childMemoryNode )
{
$list->addNode( $childMemoryNode->node );
$this->addChildNodesDepthFirst( $list, $childMemoryNode );
}
}
/**
* Returns the node with ID $nodeId and all its children, sorted accoring to
* the {@link http://en.wikipedia.org/wiki/Depth-first_search Depthth-first sorting}
* algorithm.
*
* @param string $nodeId
* @return ezcTreeNodeList
*/
public function fetchSubtreeDepthFirst( $nodeId )
{
$list = new ezcTreeNodeList;
$memoryNode = $this->getNodeById( $nodeId );
$list->addNode( $memoryNode->node );
$this->addChildNodesDepthFirst( $list, $memoryNode );
return $list;
}
/**
* Alias for fetchSubtreeDepthFirst().
*
* @param string $nodeId
* @return ezcTreeNodeList
*/
public function fetchSubtree( $nodeId )
{
return $this->fetchSubtreeDepthFirst( $nodeId );
}
/**
* Adds the children nodes of the node $memoryNode to the
* ezcTreeNodeList $list.
*
* @param ezcTreeNodeList $list
* @param ezcTreeMemoryNode $memoryNode
*/
private function addChildNodesBreadthFirst( ezcTreeNodeList $list, ezcTreeMemoryNode $memoryNode )
{
foreach ( $memoryNode->children as $nodeId => $childMemoryNode )
{
$list->addNode( $childMemoryNode->node );
}
foreach ( $memoryNode->children as $nodeId => $childMemoryNode )
{
$this->addChildNodesBreadthFirst( $list, $childMemoryNode );
}
}
/**
* Returns the node with ID $nodeId and all its children, sorted according to
* the {@link http://en.wikipedia.org/wiki/Breadth-first_search Breadth-first sorting}
* algorithm.
*
* @param string $nodeId
* @return ezcTreeNodeList
*/
public function fetchSubtreeBreadthFirst( $nodeId )
{
$list = new ezcTreeNodeList;
$memoryNode = $this->getNodeById( $nodeId );
$list->addNode( $memoryNode->node );
$this->addChildNodesBreadthFirst( $list, $memoryNode );
return $list;
}
/**
* Returns the number of direct children of the node with ID $nodeId.
*
* @param string $nodeId
* @return int
*/
public function getChildCount( $nodeId )
{
return count( $this->getNodeById( $nodeId )->children );
}
/**
* Helper method that iterates recursively over the children of $node to
* count the number of children.
*
* @param integer $count
* @param ezcTreeMemoryNode $node
*/
private function countChildNodes( &$count, ezcTreeMemoryNode $node )
{
foreach ( $node->children as $nodeId => $node )
{
$count++;
$this->countChildNodes( $count, $node );
}
}
/**
* Returns the number of children of the node with ID $nodeId, recursively
*
* @param string $nodeId
* @return int
*/
public function getChildCountRecursive( $nodeId )
{
$count = 0;
$node = $this->getNodeById( $nodeId );
$this->countChildNodes( $count, $node );
return $count;
}
/**
* Returns the distance from the root node to the node with ID $nodeId
*
* @param string $nodeId
* @return int
*/
public function getPathLength( $nodeId )
{
$childNode = $this->getNodeById( $nodeId );
$length = -1;
while ( $childNode !== null )
{
$childNode = $childNode->parent;
$length++;
}
return $length;
}
/**
* Returns whether the node with ID $nodeId has children
*
* @param string $nodeId
* @return bool
*/
public function hasChildNodes( $nodeId )
{
return count( $this->getNodeById( $nodeId )->children ) > 0;
}
/**
* Returns whether the node with ID $childId is a direct child of the node
* with ID $parentId
*
* @param string $childId
* @param string $parentId
* @return bool
*/
public function isChildOf( $childId, $parentId )
{
$childNode = $this->getNodeById( $childId );
$parentNode = $this->getNodeById( $parentId );
if ( $childNode->parent->node === $parentNode->node )
{
return true;
}
return false;
}
/**
* Returns whether the node with ID $childId is a direct or indirect child
* of the node with ID $parentId
*
* @param string $childId
* @param string $parentId
* @return bool
*/
public function isDescendantOf( $childId, $parentId )
{
$childNode = $this->getNodeById( $childId );
$parentNode = $this->getNodeById( $parentId );
if ( $childNode === $parentNode )
{
return false;
}
while ( $childNode !== null )
{
if ( $childNode->node === $parentNode->node )
{
return true;
}
$childNode = $childNode->parent;
}
return false;
}
/**
* Returns whether the nodes with IDs $child1Id and $child2Id are siblings
* (ie, the share the same parent)
*
* @param string $child1Id
* @param string $child2Id
* @return bool
*/
public function isSiblingOf( $child1Id, $child2Id )
{
$elem1 = $this->getNodeById( $child1Id );
$elem2 = $this->getNodeById( $child2Id );
return (
( $child1Id !== $child2Id ) &&
( $elem1->parent === $elem2->parent )
);
}
/**
* Sets a new node as root node, this wipes also out the whole tree
*
* @param ezcTreeNode $node
*/
public function setRootNode( ezcTreeNode $node )
{
// wipe nodelist and data
$this->nodeList = array();
$this->store->deleteDataForAllNodes();
// replace root node
$newObj = new ezcTreeMemoryNode( $node, array() );
$this->rootNode = $newObj;
// Add to node list
$this->nodeList[$node->id] = $newObj;
}
/**
* Returns the root node
*
* This methods returns null if there is no root node.
*
* @return ezcTreeNode
*/
public function getRootNode()
{
if ( $this->rootNode )
{
return $this->rootNode->node;
}
return null;
}
/**
* Adds the node $childNode as child of the node with ID $parentId
*
* @param string $parentId
* @param ezcTreeNode $childNode
*/
public function addChild( $parentId, ezcTreeNode $childNode )
{
if ( $this->inTransaction )
{
$this->addTransactionItem( new ezcTreeTransactionItem( ezcTreeTransactionItem::ADD, $childNode, null, $parentId ) );
return;
}
// Locate parent node
$parentMemoryNode = $this->getNodeById( $parentId );
// Create new node
$newObj = new ezcTreeMemoryNode( $childNode, array(), $parentMemoryNode );
// Append to parent node
$parentMemoryNode->children[$childNode->id] = $newObj;
// Add to node list
$this->nodeList[$childNode->id] = $newObj;
}
/**
* Deletes the node with ID $nodeId from the tree, including all its children
*
* @param string $nodeId
*/
public function delete( $nodeId )
{
if ( $this->inTransaction )
{
$this->addTransactionItem( new ezcTreeTransactionItem( ezcTreeTransactionItem::DELETE, null, $nodeId ) );
return;
}
// locate node to move
$nodeToDelete = $this->getNodeById( $nodeId );
// fetch the whole subtree and delete all the associated data
$children = $nodeToDelete->node->fetchSubtree();
$this->store->deleteDataForNodes( $children );
// Use the parent to remove the child
unset( $nodeToDelete->parent->children[$nodeId] );
// Remove the node and all its children
foreach ( new ezcTreeNodeListIterator( $this, $children ) as $nodeId => $data )
{
unset( $this->nodeList[$nodeId] );
}
}
/**
* Moves the node with ID $nodeId as child to the node with ID $targetParentId
*
* @param string $nodeId
* @param string $targetParentId
*/
public function move( $nodeId, $targetParentId )
{
if ( $this->inTransaction )
{
$this->addTransactionItem( new ezcTreeTransactionItem( ezcTreeTransactionItem::MOVE, null, $nodeId, $targetParentId ) );
return;
}
// locate node to move
$nodeToMove = $this->getNodeById( $nodeId );
// locate new parent
$newParent = $this->getNodeById( $targetParentId );
// new placement for node
$newParent->children[$nodeId] = $nodeToMove;
// remove old location from previous parent
unset( $nodeToMove->parent->children[$nodeId] );
// update parent attribute of the node
$nodeToMove->parent = $newParent;
}
/**
* Fixates the transaction.
*/
public function fixateTransaction()
{
}
}
?>