<?php
/**
* @copyright (c) 2013
* @package Diba
* @subpackage GenericTree
*/
namespace Diba\GenericTree;
use Diba\Tree\Node as INode;
use Diba\Tree\NodeList as INodeList;
use ArrayIterator;
use InvalidArgumentException;
use OutOfBoundsException;
/**
* Implementación de \Diba\Tree\NodeList.
*/
class NodeList implements INodeList
{
/**
* El comparador para odernar los nodos.
*
* @var callable|int
*/
protected $comparator;
/**
* Colección de nodos.
*
* @var array
*/
protected $nodes = [];
// \ArrayAccess
/**
* Indica si un índice existe.
*
* @see http://php.net/arrayaccess.offsetexists
*
* @param int $index El índice a ser comprobado.
* @return bool
*/
public function offsetExists($index)
{
return (int
) $index >= 0 && (int
) $index < count($this->nodes); }
/**
* Devuelve el nodo contenido por el índice especificado.
*
* @see http://php.net/arrayaccess.offsetget
*
* @param int $index El índice del nodo a devolver.
* @return \Diba\Tree\Node
* @throws \OutOfBoundsException
* Si el índice está fuera de rango.
*/
public function offsetGet($index)
{
$index = (int) $index;
if ($index < 0 || $index >= count($this->nodes)) { throw new OutOfBoundsException('Index is out of range');
}
return $this->nodes[$index];
}
/**
* Inserta un nodo en el índice especificado, y mueve todos los nodos
* siguientes hacia la derecha, incrementando su índice en: $index + 1.
*
* @see http://php.net/arrayaccess.offsetset
*
* @param int $index Índice donde se insertará el nodo.
* @param mixed $node El nodo a insertar.
* @return void
* @throws \InvalidArgumentException
* Si el nodo no implementa a \Diba\Tree\Node.
* @throws \OutOfBoundsException
* Si el índice está fuera de rango.
*/
public function offsetSet($index, $node)
{
if (!$node instanceof INode) {
throw new InvalidArgumentException(
'Invalid node type: instance of "\Diba\Tree\Node" expected, '
);
}
if ($index === null) {
$this->nodes[] = $node;
return;
} elseif (!$this->offsetExists($index)) {
throw new OutOfBoundsException('Index is out of range');
}
$index = (int) $index;
if ($index === 0) {
return;
}
$this->nodes[] = $node;
}
/**
* Quita el nodo contenido por el índice especificado, y mueve todos los
* nodos siguientes hacia la izquierda, reduciendo su índice en: $index - 1.
*
* @see http://php.net/arrayaccess.offsetunset
*
* @param int $index Índice del nodo a ser quitado.
* @return void
* @throws \OutOfBoundsException
* Si el índice está fuera de rango.
*/
public function offsetUnset($index)
{
$index = (int) $index;
if ($index < 0 || $index >= count($this->nodes)) { throw new OutOfBoundsException('Index is out of range');
}
}
// \Countable
/**
* Devuelve el número de nodos en ésta colección.
*
* @see http://php.net/class.countable
*
* @return int
*/
{
return count($this->nodes); }
// \IteratorAggregate
/**
* Devuelve un iterador externo.
*
* @see http://php.net/class.iteratoraggregate
*
* @return \Iterator
*/
public function getIterator()
{
return new ArrayIterator($this->nodes);
}
// \Diba\Tree\NodeList
/**
* Agrega un nodo a la colección.
*
* @param \Diba\Tree\Node $node El nodo que será agregado.
* @return void
*/
public function add(INode $node)
{
$this->nodes[] = $node;
}
/**
* Limpia la colección de nodos.
*
* @return void
*/
public function clear()
{
$this->nodes = [];
}
/**
* Indica si la colección contiene el nodo especificado.
*
* @param \Diba\Tree\Node $node El nodo a buscar.
* @return bool
*/
public function contains(INode $node)
{
return ($search !== false);
}
/**
* Indica si la colección de nodos está vacía.
*
* @return bool
*/
public function isEmpty()
{
return empty($this->nodes); }
/**
* Quita el nodo especificado de la colección.
*
* @param \Diba\Tree\Node $node El nodo a quitar.
* @return void
*/
public function remove(INode $node)
{
$previousCount = count($this->nodes);
while (($index = $this->indexOf($node)) > -1) {
unset($this->nodes[$index]); }
if ($previousCount < count($this->nodes)) { }
}
/**
* Devuelve una copia de la colección, como array.
*
* @return array
*/
public function toArray()
{
return $this->nodes;
}
// \Diba\GenericTree\NodeList
/**
* Inserta un nodo en el índice especificado, y mueve todos los nodos
* siguientes hacia la derecha, incrementando su índice en: $index + 1.
*
* @param int $index Índice del nodo a ser insertado.
* @param \Diba\Tree\Node $node El nodo a insertar.
* @return void
* @throws \OutOfBoundsException
* Si el índice está fuera de rango.
*/
public function addAt($index, INode $node)
{
$this->offsetSet($index, $node);
}
/**
* Devuelve el nodo contenido por el índice especificado.
*
* @param int $index El índice del nodo a obtener.
* @return \Diba\Tree\Node
* @throws \OutOfBoundsException
* Si el índice está fuera de rango.
*/
public function get($index)
{
return $this->offsetGet($index);
}
/**
* Devuelve el comparador de nodos.
*
* @return callable|int
*/
public function getComparator()
{
return $this->comparator;
}
/**
* Devuelve el primer índice de un nodo, si existe.
*
* @param \Diba\Tree\Node $node El nodo a buscar.
* @return int -1 si el índice no existe.
*/
public function indexOf(INode $node)
{
return ($search !== false ? $search : -1);
}
/**
* Une la colección de nodos especificada con ésta mísma.
*
* @param \Diba\Tree\NodeList $nodeList La colección de nodos a unir.
* @return bool Verdadero si ésta colección ha cambiado.
*/
public function merge(INodeList $nodeList)
{
$previousCount = count($this->nodes);
// Manipular hasta 3 nodos manualmente
switch ($nodeList->count()) { case 0:
return false;
break;
case 1:
$this->nodes[] = $nodeList->get(0);
break;
case 2:
$this->nodes[] = $nodeList->get(0);
$this->nodes[] = $nodeList->get(1);
break;
case 3:
$this->nodes[] = $nodeList->get(0);
$this->nodes[] = $nodeList->get(1);
$this->nodes[] = $nodeList->get(2);
break;
default:
if ($previousCount < 1) {
$this->nodes = $nodeList->toArray();
} else {
}
break;
}
return count($this->nodes) > $previousCount; }
/**
* Quita el nodo contenido por el índice especificado, y mueve todos los
* nodos siguientes hacia la izquierda, reduciendo su índice en: $index - 1.
*
* @param int $index Índice del nodo a ser quitado.
* @return \Diba\Tree\Node El nodo previo en el índice especificado.
* @throws \OutOfBoundsException
* Si el índice está fuera de rango.
*/
public function removeAt($index)
{
$previousNode = $this->offsetGet($index);
$this->offsetUnset($index);
return $previousNode;
}
/**
* Establece el nodo en el índice especificado, y sobreescribe al anterior.
*
* @param int $index El índice del nodo a establecer.
* @param \Diba\Tree\Node $node El nodo a establecer.
* @return \Diba\Tree\Node El nodo previo, si existía.
* @throws \OutOfBoundsException
* Si el índice está fuera de rango.
*/
public function set($index, INode $node)
{
$previousNode = $this->offsetGet($index);
$this->nodes[$index] = $node;
return $previousNode;
}
/**
* Establece el nodo padre para todos los nodos de ésta colección.
*
* @param \Diba\Tree\Node $node El nodo padre.
* @return bool Verdadero si la ejecución ha cambiado a los nodos.
*/
public function setParent(INode $node)
{
// Manipular hasta 3 nodos manualmente
switch (count($this->nodes)) { case 0:
return false;
break;
case 1:
$this->nodes[0]->setParent($node);
break;
case 2:
$this->nodes[0]->setParent($node);
$this->nodes[1]->setParent($node);
break;
case 3:
$this->nodes[0]->setParent($node);
$this->nodes[1]->setParent($node);
$this->nodes[2]->setParent($node);
break;
default:
foreach ($this->nodes as $childNode) {
$childNode->setParent($node);
}
break;
}
return true;
}
/**
* Establece el comparador, y devuelve el anterior.
*
* @param callable|int $comparator El callable o integer "flags".
* @return callable|int
* @throws \InvalidArgumentException
* Cuando el argumento $comparator no es de tipo callable o integer.
*/
public function setComparator($comparator)
{
$previousComparator = $this->comparator;
$this->comparator = $comparator;
return $previousComparator;
} else {
throw new InvalidArgumentException
(sprintf( 'Invalid $comparator type: callable/int expected, %s given',
));
}
}
/**
* Ordena los nodos de acuerdo al comparador establecido.
*
* @return void
*/
{
usort($this->nodes, $this->comparator); } else {
sort($this->nodes, $this->comparator); }
}
}