diff --git a/src/DataStructures/AbstractStrictList.php b/src/DataStructures/AbstractStrictList.php
new file mode 100644
index 0000000..5921eab
--- /dev/null
+++ b/src/DataStructures/AbstractStrictList.php
@@ -0,0 +1,298 @@
+
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+declare(strict_types=1);
+
+namespace OCC\Basics\DataStructures;
+
+use Countable;
+use InvalidArgumentException;
+use Iterator;
+use OCC\Basics\Traits\Getter;
+use Serializable;
+
+/**
+ * Abstract class for a type-sensitive list of items.
+ *
+ * @author Sebastian Meyer
+ * @package opencultureconsulting/basics
+ * @implements \Countable
+ * @implements \Iterator
+ * @implements \Serializable
+ */
+abstract class AbstractStrictList implements Countable, Iterator, Serializable
+{
+ use Getter;
+
+ /**
+ * The items.
+ */
+ protected array $items = [];
+
+ /**
+ * Count of iterations.
+ */
+ protected int $iterations = 0;
+
+ /**
+ * Defines the allowed types for items.
+ * If empty, all types are allowed.
+ * Possible values are:
+ * - "array"
+ * - "bool"
+ * - "callable"
+ * - "countable"
+ * - "float" or "double"
+ * - "int" or "integer" or "long"
+ * - "iterable"
+ * - "null"
+ * - "numeric"
+ * - "object" or FQCN
+ * - "resource"
+ * - "scalar"
+ * - "string"
+ * Fully qualified class names (FQCN) can be specified instead of the
+ * generic type "object".
+ */
+ protected array $allowedTypes = [];
+
+ /**
+ * Append items.
+ *
+ * @param mixed ...$items One or more items to add
+ *
+ * @return void
+ */
+ public function append(mixed ...$items): void
+ {
+ if (!empty($this->allowedTypes)) {
+ foreach ($items as $count => $item) {
+ if (!$this->isAllowedType($item)) {
+ throw new InvalidArgumentException('Item ' . $count + 1 . ' must be an allowed type, ' . get_debug_type($item) . ' given.');
+ }
+ }
+ }
+ $this->items = array_merge($this->items, $items);
+ }
+
+ /**
+ * Get the number of items.
+ * @see Countable::count
+ *
+ * @return int The number of items
+ */
+ public function count(): int
+ {
+ return count($this->items);
+ }
+
+ /**
+ * Clear all items.
+ *
+ * @return void
+ */
+ public function clear(): void
+ {
+ $this->items = [];
+ $this->rewind();
+ }
+
+ /**
+ * Get and remove the current item.
+ * @see Iterator::current
+ *
+ * @return mixed The current item or NULL if empty
+ */
+ abstract public function current(): mixed;
+
+ /**
+ * Check if an item is an allowed type.
+ *
+ * @param mixed $item The item to check
+ *
+ * @return bool Whether the item is an allowed type
+ */
+ protected function isAllowedType(mixed $item): bool
+ {
+ if (empty($this->allowedTypes)) {
+ return true;
+ }
+ foreach ($this->allowedTypes as $type) {
+ $function = 'is_' . $type;
+ $fqcn = '\\' . ltrim($type, '\\');
+ if (function_exists($function) && $function($item)) {
+ return true;
+ }
+ if (is_object($item) && is_a($item, $fqcn)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Get the number of the current iteration.
+ * @see Iterator::key
+ *
+ * @return int The number of the current iteration
+ */
+ public function key(): int
+ {
+ return $this->iterations;
+ }
+
+ /**
+ * Magic getter method for $this->allowedTypes.
+ * @see OCC\Basics\Traits\Getter
+ *
+ * @return array The list of allowed item types
+ */
+ protected function magicGetAllowedTypes(): array
+ {
+ return $this->allowedTypes;
+ }
+
+ /**
+ * Count the next iteration.
+ * @see Iterator::next
+ *
+ * @return void
+ */
+ public function next(): void
+ {
+ ++$this->iterations;
+ }
+
+ /**
+ * Get the current item without removing it.
+ *
+ * @return mixed The current item or NULL if empty
+ */
+ abstract public function peek(): mixed;
+
+ /**
+ * Reset the iterator position.
+ * @see Iterator::rewind
+ *
+ * @return void
+ */
+ public function rewind(): void
+ {
+ $this->iterations = 0;
+ }
+
+ /**
+ * Get string representation of $this.
+ * @see Serializable::serialize
+ *
+ * @return ?string String representation
+ */
+ public function serialize(): ?string
+ {
+ return serialize($this->__serialize());
+ }
+
+ /**
+ * Restore $this from string representation.
+ * @see Serializable::unserialize
+ *
+ * @param string $data String representation
+ *
+ * @return void
+ */
+ public function unserialize($data): void
+ {
+ $this->__unserialize(unserialize($data));
+ }
+
+ /**
+ * Check if there are any items.
+ * @see Iterator::valid
+ *
+ * @return bool Whether there are items
+ */
+ public function valid(): bool
+ {
+ return (bool) $this->items;
+ }
+
+ /**
+ * Reset iteration counter after cloning.
+ *
+ * @return void
+ */
+ public function __clone(): void
+ {
+ $this->rewind();
+ }
+
+ /**
+ * Create a (type-sensitive) traversable set of items.
+ *
+ * @param array $items Initial set of items
+ * @param string[] $allowedTypes Allowed types of items (optional)
+ */
+ public function __construct(array $items = [], array $allowedTypes = [])
+ {
+ if (!empty($allowedTypes)) {
+ if (array_sum(array_map('is_string', $allowedTypes)) !== count($allowedTypes)) {
+ throw new InvalidArgumentException('Allowed types must be array of strings or empty array.');
+ }
+ $this->allowedTypes = $allowedTypes;
+ }
+ $this->append(...$items);
+ }
+
+ /**
+ * Get debug information for $this.
+ *
+ * @return array Array of debug information
+ */
+ public function __debugInfo(): array
+ {
+ return $this->__serialize();
+ }
+
+ /**
+ * Get array representation of $this.
+ *
+ * @return array Array representation
+ */
+ public function __serialize(): array
+ {
+ return [
+ 'allowedTypes' => $this->allowedTypes,
+ 'items' => $this->items
+ ];
+ }
+
+ /**
+ * Restore $this from array representation.
+ *
+ * @param array $data Array representation
+ *
+ * @return void
+ */
+ public function __unserialize(array $data): void
+ {
+ $this->allowedTypes = $data['allowedTypes'] ?? [];
+ $this->items = $data['items'] ?? [];
+ }
+}
diff --git a/src/DataStructures/Queue.php b/src/DataStructures/Queue.php
deleted file mode 100644
index bc98916..0000000
--- a/src/DataStructures/Queue.php
+++ /dev/null
@@ -1,303 +0,0 @@
-
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation, either version 3 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see .
- */
-
-declare(strict_types=1);
-
-namespace OCC\Basics\DataStructures;
-
-use ArrayAccess;
-use Countable;
-use InvalidArgumentException;
-use OCC\Basics\Traits\Getter;
-use OutOfBoundsException;
-use OutOfRangeException;
-use SeekableIterator;
-
-/**
- * Handles an ordered queue of items - optionally type-sensitive.
- *
- * @author Sebastian Meyer
- * @package opencultureconsulting/basics
- * @implements ArrayAccess
- * @implements Countable
- * @implements SeekableIterator
- */
-class Queue implements ArrayAccess, Countable, SeekableIterator
-{
- use Getter;
-
- /**
- * Holds the queue's elements.
- */
- protected array $queue = [];
-
- /**
- * Holds the queue's current index.
- */
- protected int $index = 0;
-
- /**
- * Defines the allowed types for the queue's elements.
- * If empty, all types are allowed.
- * Possible values are:
- * - "array"
- * - "bool"
- * - "callable"
- * - "countable"
- * - "float" or "double"
- * - "int" or "integer" or "long"
- * - "iterable"
- * - "null"
- * - "numeric"
- * - "object" or FQCN
- * - "resource"
- * - "scalar"
- * - "string"
- * Fully qualified class names (FQCN) can be specified instead of the
- * generic type "object".
- */
- protected array $allowedTypes = [];
-
- /**
- * Check if a variable is an allowed type.
- *
- * @param mixed $var The variable to check
- *
- * @return bool Whether the variable is an allowed type
- */
- protected function isAllowedType(mixed $var): bool
- {
- if (empty($this->allowedTypes)) {
- return true;
- }
- foreach ($this->allowedTypes as $type) {
- $function = 'is_' . $type;
- $fqcn = '\\' . ltrim($type, '\\');
- if (function_exists($function) && $function($var)) {
- return true;
- }
- if (is_object($var) && is_a($var, $fqcn)) {
- return true;
- }
- }
- return false;
- }
-
- /**
- * Checks if a given index is in the range of valid indexes.
- *
- * @param mixed $offset The index to check
- * @param bool $allowAppend Should the next free index be valid as well?
- *
- * @return bool Whether the given index is in valid range
- */
- protected function isIndexInRange(mixed $offset, bool $allowAppend = false): bool
- {
- $options = [
- 'options' => [
- 'min_range' => 0,
- 'max_range' => count($this->queue) - ($allowAppend ? 0 : 1)
- ]
- ];
- return filter_var($offset, FILTER_VALIDATE_INT, $options) !== false;
- }
-
- /**
- * Check if a given index exists on the queue.
- * @see ArrayAccess::offsetExists
- *
- * @param int $offset The queue's index to check
- *
- * @return bool Whether the given index is valid
- */
- public function offsetExists(mixed $offset): bool
- {
- return isset($this->queue[$offset]);
- }
-
- /**
- * Get the element with given index from the queue.
- * @see ArrayAccess::offsetGet
- *
- * @param int $offset The queue's index to get
- *
- * @return ?mixed The queue's element at given index or NULL
- */
- public function offsetGet(mixed $offset): mixed
- {
- return $this->queue[$offset] ?? null;
- }
-
- /**
- * Set the element at given index in the queue.
- * @see ArrayAccess::offsetSet
- *
- * @param ?int $offset The queue's index to set or NULL to append
- * Must be between 0 and the length of the queue.
- * @param mixed $value The element to set at the given index
- * Must be of an allowed type if applicable.
- *
- * @return void
- *
- * @throws InvalidArgumentException
- * @throws OutOfRangeException
- */
- public function offsetSet(mixed $offset, mixed $value): void
- {
- if (is_null($offset)) {
- $offset = count($this->queue);
- } elseif (!$this->isIndexInRange($offset, true)) {
- throw new OutOfRangeException('Index must be an integer between 0 and the length of the queue (' . count($this->queue) . ') or NULL to append: ' . get_debug_type($offset) . (is_int($offset) ? ' ' . (string) $offset : '') . ' given.');
- }
- if (!$this->isAllowedType($value)) {
- throw new InvalidArgumentException('Value must be one of ' . implode(', ', $this->allowedTypes) . ': ' . get_debug_type($value) . ' given.');
- }
- $this->queue[(int) $offset] = $value;
- }
-
- /**
- * Remove the element with given index from the queue.
- * @see ArrayAccess::offsetUnset
- *
- * @param int $offset The queue's index to unset
- *
- * @return void
- */
- public function offsetUnset(mixed $offset): void
- {
- if ($this->isIndexInRange($offset)) {
- array_splice($this->queue, (int) $offset, 1);
- if ((int) $offset <= $this->index) {
- --$this->index;
- }
- }
- }
-
- /**
- * Get the number of elements in the queue.
- * @see Countable::count
- *
- * @return int The number of items in the queue
- */
- public function count(): int
- {
- return count($this->queue);
- }
-
- /**
- * Get the current element from the queue.
- * @see Iterator::current
- *
- * @return mixed|null The queue's current element or NULL
- */
- public function current(): mixed
- {
- return $this->queue[$this->index] ?? null;
- }
-
- /**
- * Get the current index from the queue.
- * @see Iterator::key
- *
- * @return int The queue's current index
- */
- public function key(): int
- {
- return $this->index;
- }
-
- /**
- * Move the index to next element of the queue.
- * @see Iterator::next
- *
- * @return void
- */
- public function next(): void
- {
- ++$this->index;
- }
-
- /**
- * Reset the index to the first element of the queue.
- * @see Iterator::rewind
- *
- * @return void
- */
- public function rewind(): void
- {
- $this->index = 0;
- }
-
- /**
- * Check if the queue's current index is valid.
- * @see Iterator::valid
- *
- * @return bool Whether the queue's current index is valid
- */
- public function valid(): bool
- {
- return isset($this->queue[$this->index]);
- }
-
- /**
- * Sets the queue's current index.
- * @see SeekableIterator::seek
- *
- * @param int $offset The queue's new index
- *
- * @return void
- *
- * @throws OutOfBoundsException
- */
- public function seek(int $offset): void
- {
- if (!$this->isIndexInRange($offset)) {
- throw new OutOfBoundsException('Index must be an integer between 0 and the length of the queue (' . count($this->queue) . '): ' . (string) $offset . ' given.');
- }
- $this->index = $offset;
- }
-
- /**
- * Magic getter method for $this->allowedTypes.
- * @see OCC\Traits\Getter
- *
- * @return array The list of the queue's allowed element types
- */
- protected function magicGetAllowedTypes(): array
- {
- return $this->allowedTypes;
- }
-
- /**
- * Create a (type-sensitive) queue of elements.
- * @see OCC\Helper\Queue::allowedTypes
- *
- * @param string[] $allowedTypes Allowed types of queue's elements
- *
- * @throws InvalidArgumentException
- */
- public function __construct(array $allowedTypes = [])
- {
- if (array_sum(array_map('is_string', $allowedTypes)) !== count($allowedTypes)) {
- throw new InvalidArgumentException('Parameter 1 must be array of strings or empty array.');
- }
- $this->allowedTypes = $allowedTypes;
- }
-}
diff --git a/src/DataStructures/StrictQueue.php b/src/DataStructures/StrictQueue.php
new file mode 100644
index 0000000..7fad134
--- /dev/null
+++ b/src/DataStructures/StrictQueue.php
@@ -0,0 +1,84 @@
+
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+declare(strict_types=1);
+
+namespace OCC\Basics\DataStructures;
+
+use Countable;
+use Iterator;
+use Serializable;
+
+/**
+ * A (type-sensitive) destructive First In, First Out Queue.
+ *
+ * @author Sebastian Meyer
+ * @package opencultureconsulting/basics
+ * @implements \Countable
+ * @implements \Iterator
+ * @implements \Serializable
+ */
+class StrictQueue extends AbstractStrictList
+{
+ /**
+ * Get and remove the first item.
+ * @see Iterator::current
+ *
+ * @return mixed The first item or NULL if empty
+ */
+ public function current(): mixed
+ {
+ return array_shift($this->items);
+ }
+
+ /**
+ * Dequeue the first item.
+ * Alias of Queue::current
+ *
+ * @return mixed The first item or NULL if empty
+ */
+ public function dequeue(): mixed
+ {
+ return $this->current();
+ }
+
+ /**
+ * Enqueue items.
+ * Alias of Queue::append
+ *
+ * @param mixed ...$items One or more items to add
+ *
+ * @return void
+ */
+ public function enqueue(mixed ...$items): void
+ {
+ $this->append(...$items);
+ }
+
+ /**
+ * Get the first item without removing it.
+ *
+ * @return mixed The first item or NULL if empty
+ */
+ public function peek(): mixed
+ {
+ return $this->items[array_key_first($this->items)] ?? null;
+ }
+}
diff --git a/src/DataStructures/StrictStack.php b/src/DataStructures/StrictStack.php
new file mode 100644
index 0000000..5a21813
--- /dev/null
+++ b/src/DataStructures/StrictStack.php
@@ -0,0 +1,84 @@
+
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ */
+
+declare(strict_types=1);
+
+namespace OCC\Basics\DataStructures;
+
+use Countable;
+use Iterator;
+use Serializable;
+
+/**
+ * A (type-sensitive) destructive Last In, First Out Stack.
+ *
+ * @author Sebastian Meyer
+ * @package opencultureconsulting/basics
+ * @implements \Countable
+ * @implements \Iterator
+ * @implements \Serializable
+ */
+class StrictStack extends AbstractStrictList
+{
+ /**
+ * Get and remove the last item.
+ * @see Iterator::current
+ *
+ * @return mixed The last item or NULL if empty
+ */
+ public function current(): mixed
+ {
+ return array_pop($this->items);
+ }
+
+ /**
+ * Get the last item without removing it.
+ *
+ * @return mixed The first item or NULL if empty
+ */
+ public function peek(): mixed
+ {
+ return $this->items[array_key_last($this->items)] ?? null;
+ }
+
+ /**
+ * Stack items.
+ * Alias of Stack::append
+ *
+ * @param mixed ...$items One or more items to add
+ *
+ * @return void
+ */
+ public function stack(mixed ...$items): void
+ {
+ $this->append(...$items);
+ }
+
+ /**
+ * Unstack the last item.
+ * Alias of Stack::current
+ *
+ * @return mixed The last item or NULL if empty
+ */
+ public function unstack(): mixed
+ {
+ return $this->current();
+ }
+}