/**
  * Makes the first node the parent of the second node.
  * @access public
  * @param mixed parentIdValue The string id of the node to add as a parent.
  * @param mixed childIdValue The string id of the child node.
  * @return void
  **/
 function addParent($parentIdValue, $childIdValue)
 {
     // ** parameter validation
     ArgumentValidator::validate($parentIdValue, OrValidatorRule::getRule(NonzeroLengthStringValidatorRule::getRule(), IntegerValidatorRule::getRule()), true);
     ArgumentValidator::validate($childIdValue, OrValidatorRule::getRule(NonzeroLengthStringValidatorRule::getRule(), IntegerValidatorRule::getRule()), true);
     // ** end of parameter validation
     // get the two nodes
     $parent = $this->getNode($parentIdValue);
     $child = $this->getNode($childIdValue);
     // the next two calls make sure everything will go smoothly
     // i.e. will help to detect that $parent is not already a parent of $child
     $parent->getChildren();
     $child->getParents();
     $this->traverse($child->getId(), true, -1);
     // traverse fully down in order to detect cycles
     // get the tree nodes
     $parentTreeNode = $this->_tree->getNode($parentIdValue);
     $childTreeNode = $this->_tree->getNode($childIdValue);
     // make sure that we are not adding a second parent in a single-parent hierarchy
     if (!$this->_allowsMultipleParents) {
         if ($childTreeNode->getParentsCount() > 1) {
             throwError(new Error(HierarchyException::SINGLE_PARENT_HIERARCHY(), "HierarchyCache", true));
         }
     }
     // IMPORTANT SPECIAL CASES:
     // A = the number of levels that the parent is cached down
     $A = $this->_cache[$parentIdValue][1];
     // B = the number of levels that the child is cached down
     $B = $this->_cache[$childIdValue][1];
     // if B < A-1 AND B >= 0 then cache down the child A-1 levels
     if ($B < $A - 1 && $B >= 0) {
         $this->_traverseDown($childIdValue, $A - 1);
     }
     // if A < 0 AND B >= 0 then cache down the child fully
     if ($A < 0 && $B >= 0) {
         $this->_traverseDown($childIdValue, -1);
     }
     // the special cases are symmetric when going up
     // A = the number of levels that the child is cached up
     $A = $this->_cache[$childIdValue][2];
     // B = the number of levels that the parent is cached up
     $B = $this->_cache[$parentIdValue][2];
     // if B < A-1 AND B >= 0 then cache up the parent A-1 levels
     if ($B < $A - 1 && $B >= 0) {
         $this->_traverseUp($parentIdValue, $A - 1);
     }
     // if A < 0 AND B >= 0 then cache up the parent fully
     if ($A < 0 && $B >= 0) {
         $this->_traverseUp($parentIdValue, -1);
     }
     // now add the new node as a parent
     // 1) update the cache
     $this->_tree->addNode($childTreeNode, $parentTreeNode, true);
     // 2) update the database
     if (isset($this->harmoni_db)) {
         if (!isset($this->addParent_stmt)) {
             $query = $this->harmoni_db->insert();
             $query->setTable("az2_j_node_node");
             $query->addRawValue("fk_hierarchy", '?');
             $query->addRawValue("fk_parent", '?');
             $query->addRawValue("fk_child", '?');
             $this->addParent_stmt = $query->prepare();
         }
         $this->addParent_stmt->bindValue(1, $this->_hierarchyId);
         $this->addParent_stmt->bindValue(2, $parentIdValue);
         $this->addParent_stmt->bindValue(3, $childIdValue);
         $this->addParent_stmt->execute();
         $queryResult = $this->addParent_stmt->getResult();
     } else {
         $dbHandler = Services::getService("DatabaseManager");
         $query = new InsertQuery();
         $query->setTable("az2_j_node_node");
         $query->addValue("fk_hierarchy", $this->_hierarchyId);
         $query->addValue("fk_parent", $parentIdValue);
         $query->addValue("fk_child", $childIdValue);
         //		echo "<pre>\n";
         //		echo MySQL_SQLGenerator::generateSQLQuery($query);
         //		echo "</pre>\n";
         $queryResult = $dbHandler->query($query, $this->_dbIndex);
     }
     if ($queryResult->getNumberOfRows() != 1) {
         throw new OperationFailedException($queryResult->getNumberOfRows() . " rows found. Expecting 1.");
     }
     // Update the ancestory table
     $this->rebuildSubtreeAncestory($child->getId());
 }
 /**
  * Traverse a Hierarchy returning information about each Node encountered.
  * 
  * @param object Id $startId
  * @param int $mode
  * @param int $direction
  * @param int $levels
  *	
  * @return object TraversalInfoIterator
  * 
  * @throws object HierarchyException An exception with one of
  *		   the following messages defined in
  *		   org.osid.hierarchy.HierarchyException may be thrown:	 {@link
  *		   org.osid.hierarchy.HierarchyException#OPERATION_FAILED
  *		   OPERATION_FAILED}, {@link
  *		   org.osid.hierarchy.HierarchyException#PERMISSION_DENIED
  *		   PERMISSION_DENIED}, {@link
  *		   org.osid.hierarchy.HierarchyException#CONFIGURATION_ERROR
  *		   CONFIGURATION_ERROR}, {@link
  *		   org.osid.hierarchy.HierarchyException#UNIMPLEMENTED
  *		   UNIMPLEMENTED}, {@link
  *		   org.osid.hierarchy.HierarchyException#NODE_TYPE_NOT_FOUND
  *		   NODE_TYPE_NOT_FOUND}, {@link
  *		   org.osid.hierarchy.HierarchyException#UNKNOWN_TRAVERSAL_MODE
  *		   UNKNOWN_TRAVERSAL_MODE}, {@link
  *		   org.osid.hierarchy.HierarchyException#UNKNOWN_TRAVERSAL_DIRECTION
  *		   UNKNOWN_TRAVERSAL_DIRECTION}
  * 
  * @access public
  */
 function traverse(Id $startId, $mode, $direction, $levels)
 {
     // Check the arguments
     ArgumentValidator::validate($mode, IntegerValidatorRule::getRule());
     ArgumentValidator::validate($direction, IntegerValidatorRule::getRule());
     ArgumentValidator::validate($levels, IntegerValidatorRule::getRule());
     if ($mode != Hierarchy::TRAVERSE_MODE_DEPTH_FIRST) {
         // Only depth-first traversal is supported in the current implementation.
         throwError(new Error(HierarchyException::UNKNOWN_TRAVERSAL_DIRECTION(), "Hierarchy", true));
     }
     $down = $direction == Hierarchy::TRAVERSE_DIRECTION_DOWN;
     $result = $this->_cache->traverse($startId, $down, $levels);
     return $result;
 }