1<?php
2
3/**
4 * This file is part of the Nette Framework (https://nette.org)
5 * Copyright (c) 2004 David Grudl (https://davidgrudl.com)
6 */
7
8declare(strict_types=1);
9
10namespace Nette\Utils;
11
12use Nette;
13
14
15/**
16 * Provides the base class for a generic list (items can be accessed by index).
17 * @template T
18 * @implements \IteratorAggregate<int, T>
19 * @implements \ArrayAccess<int, T>
20 */
21class ArrayList implements \ArrayAccess, \Countable, \IteratorAggregate
22{
23	use Nette\SmartObject;
24
25	private array $list = [];
26
27
28	/**
29	 * Transforms array to ArrayList.
30	 * @param  list<T>  $array
31	 */
32	public static function from(array $array): static
33	{
34		if (!Arrays::isList($array)) {
35			throw new Nette\InvalidArgumentException('Array is not valid list.');
36		}
37
38		$obj = new static;
39		$obj->list = $array;
40		return $obj;
41	}
42
43
44	/**
45	 * Returns an iterator over all items.
46	 * @return \Iterator<int, T>
47	 */
48	public function &getIterator(): \Iterator
49	{
50		foreach ($this->list as &$item) {
51			yield $item;
52		}
53	}
54
55
56	/**
57	 * Returns items count.
58	 */
59	public function count(): int
60	{
61		return count($this->list);
62	}
63
64
65	/**
66	 * Replaces or appends a item.
67	 * @param  int|null  $index
68	 * @param  T  $value
69	 * @throws Nette\OutOfRangeException
70	 */
71	public function offsetSet($index, $value): void
72	{
73		if ($index === null) {
74			$this->list[] = $value;
75
76		} elseif (!is_int($index) || $index < 0 || $index >= count($this->list)) {
77			throw new Nette\OutOfRangeException('Offset invalid or out of range');
78
79		} else {
80			$this->list[$index] = $value;
81		}
82	}
83
84
85	/**
86	 * Returns a item.
87	 * @param  int  $index
88	 * @return T
89	 * @throws Nette\OutOfRangeException
90	 */
91	public function offsetGet($index): mixed
92	{
93		if (!is_int($index) || $index < 0 || $index >= count($this->list)) {
94			throw new Nette\OutOfRangeException('Offset invalid or out of range');
95		}
96
97		return $this->list[$index];
98	}
99
100
101	/**
102	 * Determines whether a item exists.
103	 * @param  int  $index
104	 */
105	public function offsetExists($index): bool
106	{
107		return is_int($index) && $index >= 0 && $index < count($this->list);
108	}
109
110
111	/**
112	 * Removes the element at the specified position in this list.
113	 * @param  int  $index
114	 * @throws Nette\OutOfRangeException
115	 */
116	public function offsetUnset($index): void
117	{
118		if (!is_int($index) || $index < 0 || $index >= count($this->list)) {
119			throw new Nette\OutOfRangeException('Offset invalid or out of range');
120		}
121
122		array_splice($this->list, $index, 1);
123	}
124
125
126	/**
127	 * Prepends a item.
128	 * @param  T  $value
129	 */
130	public function prepend(mixed $value): void
131	{
132		$first = array_slice($this->list, 0, 1);
133		$this->offsetSet(0, $value);
134		array_splice($this->list, 1, 0, $first);
135	}
136}
137