3 namespace Org\Mxchange\CoreFramework\Lists;
5 // Import framework stuff
6 use Org\Mxchange\CoreFramework\Factory\Object\ObjectFactory;
7 use Org\Mxchange\CoreFramework\Generic\FrameworkInterface;
8 use Org\Mxchange\CoreFramework\Object\BaseFrameworkSystem;
9 use Org\Mxchange\CoreFramework\Traits\Iterator\IteratorTrait;
10 use Org\Mxchange\CoreFramework\Visitor\Visitable;
13 use \InvalidArgumentException;
14 use \IteratorAggregate;
18 * A general list class
20 * @author Roland Haeder <webmaster@shipsimu.org>
22 * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2020 Core Developer Team
23 * @license GNU GPL 3.0 or any newer version
24 * @link http://www.shipsimu.org
26 * This program is free software: you can redistribute it and/or modify
27 * it under the terms of the GNU General Public License as published by
28 * the Free Software Foundation, either version 3 of the License, or
29 * (at your option) any later version.
31 * This program is distributed in the hope that it will be useful,
32 * but WITHOUT ANY WARRANTY; without even the implied warranty of
33 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
34 * GNU General Public License for more details.
36 * You should have received a copy of the GNU General Public License
37 * along with this program. If not, see <http://www.gnu.org/licenses/>.
39 abstract class BaseList extends BaseFrameworkSystem implements IteratorAggregate, Countable {
43 // Exception constants
44 const EXCEPTION_GROUP_ALREADY_ADDED = 0xf20;
45 const EXCEPTION_GROUP_NOT_FOUND = 0xf21;
46 const EXCEPTION_INVALID_HASH = 0xf22;
51 private $listGroups = [];
56 private $listEntries = [];
61 private $listIndex = [];
64 * Protected constructor
66 * @param $className Name of the class
69 protected function __construct (string $className) {
70 // Call parent constructor
71 parent::__construct($className);
75 * Getter for iterator instance from this list
77 * @return $iteratorInstance An instance of a Iterator class
79 public function getIterator () {
80 // Get iterator from here
81 $iteratorInstance = $this->getIteratorInstance();
83 // Is the instance set?
84 if (is_null($iteratorInstance)) {
85 // Prepare a default iterator
86 $iteratorInstance = ObjectFactory::createObjectByConfiguredName('default_iterator_class', [$this]);
89 $this->setIteratorInstance($iteratorInstance);
93 return $iteratorInstance;
97 * Checks whether the given group is set
99 * @param $groupName Group to check if found in list
100 * @return $isset Whether the group is valid
102 public function isGroupSet (string $groupName) {
103 // Validate parameter
104 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: this=' . $this->__toString() . ',groupName=' . $groupName);
105 if (empty($groupName)) {
107 throw new InvalidArgumentException('Parameter "groupName" is empty');
110 // Check on existence ...
111 return isset($this->listGroups[$groupName]);
115 * Adds the given group or if already added issues a ListGroupAlreadyAddedException
117 * @param $groupName Group to add
119 * @throws ListGroupAlreadyAddedException If the given group is already added
121 public function addGroup (string $groupName) {
122 // Validate parameter
123 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: this=' . $this->__toString() . ',groupName=' . $groupName . ' - CALLED!');
124 if (empty($groupName)) {
126 throw new InvalidArgumentException('Parameter "groupName" is empty');
127 } elseif ($this->isGroupSet($groupName)) {
128 // Throw the exception here
129 throw new ListGroupAlreadyAddedException(array($this, $groupName), self::EXCEPTION_GROUP_ALREADY_ADDED);
132 // Add the group which is a simple array
133 $this->listGroups[$groupName] = ObjectFactory::createObjectByConfiguredName('list_group_class');
134 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: this=' . $this->__toString() . ',groupName=' . $groupName . ' - EXIT!');
138 * Adds the given instance to list group and sub group
140 * @param $groupName Group to add instance to
141 * @param $subGroup Sub group to add instance to
142 * @param $visitableInstance An instance of Visitable
144 * @throws NoListGroupException If the given group is not found
146 public function addInstance (string $groupName, string $subGroup, Visitable $visitableInstance) {
147 // Validate parameter
148 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: this=' . $this->__toString() . ',groupName=' . $groupName . ',subGroup=' . $subGroup . ',visitableInstance=' . $visitableInstance->__toString() . ' - CALLED!');
149 if (empty($groupName)) {
151 throw new InvalidArgumentException('Parameter "groupName" is empty');
152 } elseif (empty($subGroup)) {
154 throw new InvalidArgumentException('Parameter "subGroup" is empty');
155 } elseif (!$this->isGroupSet($groupName)) {
156 // Throw the exception here
157 throw new NoListGroupException(array($this, $groupName), self::EXCEPTION_GROUP_NOT_FOUND);
160 // Is the sub group there?
161 if (!$this->listGroups[$groupName]->isGroupSet($subGroup)) {
162 // Automatically add it
163 $this->listGroups[$groupName]->addGroup($subGroup);
167 $hash = $this->generateHash($groupName, $subGroup, $visitableInstance);
170 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: this=' . $this->__toString() . ',this->listGroups[' . $groupName . ']=' . $this->listGroups[$groupName]->__toString());
172 // Add the hash to the index
173 array_push($this->listIndex, $hash);
175 // Add the instance itself to the list
176 $this->listEntries[$hash] = $visitableInstance;
179 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: this=' . $this->__toString() . ',groupName=' . $groupName . ',subGroup=' . $subGroup . ' - EXIT!');
183 * Gets an array from given list
185 * @param $groupName The requested list
186 * @return $array The requested array
187 * @throws NoListGroupException If the given group is not found
189 public final function getArrayFromList ($groupName) {
190 // Is the group there?
191 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: this=' . $this->__toString() . ',groupName[' . gettype($groupName) . ']=' . $groupName . ' - CALLED!');
192 if (empty($groupName)) {
194 throw new InvalidArgumentException('Parameter "groupName" is empty');
195 } elseif ((!is_null($groupName)) && (!$this->isGroupSet($groupName))) {
196 // Throw the exception here
197 throw new NoListGroupException(array($this, $groupName), self::EXCEPTION_GROUP_NOT_FOUND);
203 // Is there another list?
204 if (!is_null($groupName)) {
205 // Then get it as well
206 $array = $this->listGroups[$groupName]->getArrayFromList(NULL);
209 // Walk through all entries
210 foreach ($this->listIndex as $hash) {
211 // Is the list entry set?
212 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: hash=' . $hash);
213 if ($this->isHashValid($hash)) {
215 array_push($array, $this->listEntries[$hash]);
216 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: hash=' . $hash . ',array(' . count($array) . ')=' . print_r($array, true) . ' - ADDED!');
221 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: this=' . $this->__toString() . ',groupName[' . gettype($groupName) . ']=' . $groupName . ',[]=' . count($array) . ' - EXIT!');
226 * Adds the given entry to list group
228 * @param $groupName Group to add instance to
229 * @param $entry An entry of any type
231 * @throws NoListGroupException If the given group is not found
233 public function addEntry (string $groupName, $entry) {
234 // Is the group already added?
235 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: this=' . $this->__toString() . ',groupName=' . $groupName . ' - CALLED!');
236 if (empty($groupName)) {
238 throw new InvalidArgumentException('Parameter "groupName" is empty');
239 } elseif (!$this->isGroupSet($groupName)) {
240 // Throw the exception here
241 throw new NoListGroupException(array($this, $groupName), self::EXCEPTION_GROUP_NOT_FOUND);
245 $hash = $this->generateHash($groupName, $groupName, $entry);
248 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: this=' . $this->__toString() . ',groupName=' . $groupName . ',entry=' . print_r($entry, true) . ', hash=' . $hash);
250 // Add the hash to the index
251 array_push($this->listIndex, $hash);
254 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: this=' . $this->__toString() . ',groupName=' . $groupName . ',listEntries()=' . count($this->listEntries));
256 // Now add the entry to the list
257 $this->listEntries[$hash] = $entry;
260 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: this=' . $this->__toString() . ',groupName=' . $groupName . ',listEntries()=' . count($this->listEntries) . ' - EXIT!');
264 * Removes given entry from the list group
266 * @param $groupName Group where we should remove the entry from
267 * @param $entry The entry we should remove
269 * @throws NoListGroupException If the given group is not found
271 public function removeEntry (string $groupName, $entry) {
272 // Is the group already added?
273 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: this=' . $this->__toString() . ',groupName=' . $groupName . ' - CALLED!');
274 if (empty($groupName)) {
276 throw new InvalidArgumentException('Parameter "groupName" is empty');
277 } elseif (!$this->isGroupSet($groupName)) {
278 // Throw the exception here
279 throw new NoListGroupException(array($this, $groupName), self::EXCEPTION_GROUP_NOT_FOUND);
283 $hash = $this->generateHash($groupName, $groupName, $entry);
286 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: this=' . $this->__toString() . ',groupName=' . $groupName . ',entry=' . $entry . ', hash=' . $hash);
288 // Remove it from the list ...
289 unset($this->listEntries[$hash]);
291 // ... and hash list as well
292 unset($this->listIndex[array_search($hash, $this->listIndex)]);
295 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: this=' . $this->__toString() . ',groupName=' . $groupName . ' - EXIT!');
299 * Generates a hash from given group, sub group and entry
301 * @param $groupName Group to add instance to
302 * @param $subGroup Sub group to add instance to
303 * @param $entry An entry of any type
304 * @return $hash The generated
306 private function generateHash (string $groupName, string $subGroup, $entry) {
307 // Created entry, 'null' is default
310 // Determine type of entry
311 if (is_null($entry)) {
313 } elseif ($entry instanceof FrameworkInterface) {
314 // Own instance detected
315 $entry2 = $entry->hashCode();
316 } elseif ((is_int($entry)) || (is_float($entry)) || (is_resource($entry))) {
317 // Integer/float/resource detected
318 $entry2 = gettype($entry) . ':' . $entry;
319 } elseif (is_string($entry)) {
321 $entry2 = crc32($entry) . ':' . strlen($entry);
322 } elseif ((is_array($entry)) && (isset($entry['id']))) {
323 // Supported array found
324 $entry2 = crc32($entry['id']) . ':' . count($entry);
325 } elseif (($this->getCallbackInstance() instanceof FrameworkInterface) && (method_exists($this->getCallbackInstance(), 'generateListHashFromEntry'))) {
327 $entry2 = $this->getCallbackInstance()->generateListHashFromEntry($entry);
329 // Unsupported type detected
330 self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: Entry type ' . gettype($entry) . ' is unsupported (this->callbackInstance=' . $this->getCallbackInstance() . ').');
332 // At least take all keys from array
333 $entry2 = gettype($entry) . ':' . implode(':', array_keys($entry));
336 // Construct string which we shall hash
337 $hashString = $groupName . ':' . $subGroup . ':' . $entry2;
339 // Hash it with fastest hasher
340 $hash = crc32($hashString);
347 * Clears an array of groups, all are being checked for existence
349 * @param $groupNames An array with existing list groups
352 protected function clearGroups (array $groupNames) {
353 // Walk through all groups
354 foreach ($groupNames as $groupName) {
356 $this->clearGroup($groupName);
361 * Clears a single group by resetting it to its initial state (empty array)
363 * @param $groupName Name of an existing group to clear
366 protected function clearGroup (string $groupName) {
367 // Does this group exist?
368 if (empty($groupName)) {
370 throw new InvalidArgumentException('Parameter "groupName" is empty');
371 } elseif (!$this->isGroupSet($groupName)) {
372 // Throw the exception here
373 throw new NoListGroupException(array($this, $groupName), self::EXCEPTION_GROUP_NOT_FOUND);
376 // Then clear this group list
377 $this->listGroups[$groupName]->clearList();
380 $this->listIndex = [];
381 $this->listEntries = [];
385 * Counts all entries in this list
387 * @return $count All entries in this list
389 public final function count () {
390 return count($this->listIndex);
394 * Checks whether the given hash is valid
396 * @param $hash The hash we should validate
397 * @return $isValid Whether the given hash is valid
399 public final function isHashValid (string $hash) {
400 // Validate parameter
403 throw new InvalidArgumentException('Parameter "hash" is empty');
407 $isValid = ((in_array($hash, $this->listIndex)) && (isset($this->listEntries[$hash])));
414 * Getter for hash from given hash index
416 * @param $hashIndex Index holding the hash
417 * @return $hash The hash
419 public final function getHash ($hashIndex) {
421 $hash = $this->listIndex[$hashIndex];
428 * Gets an entry from given hash index
430 * @param $hashIndex The hash index to resolve the mapped entry
431 * @return $entry Solved entry from list
432 * @throws InvalidListHashException If the solved hash index is invalid
434 public function getEntry ($hashIndex) {
435 // Get the hash value
436 $hash = $this->getHash($hashIndex);
438 // Is the hash valid?
439 if (!$this->isHashValid($hash)) {
440 // Throw an exception here
441 throw new InvalidListHashException(array($this, $hash, $hashIndex), self::EXCEPTION_INVALID_HASH);
444 // Now copy the entry
445 $entry = $this->listEntries[$hash];
452 * Gets a full array from given group name
454 * @param $groupName The group name to get a list for
455 * @return $entries The array with all entries
456 * @throws NoListGroupException If the specified group is invalid
458 public function getArrayFromProtocolInstance (string $groupName) {
459 // Is the group valid?
460 if (empty($groupName)) {
462 throw new InvalidArgumentException('Parameter "groupName" is empty');
463 } elseif (!$this->isGroupSet($groupName)) {
464 // Throw the exception here
465 throw new NoListGroupException(array($this, $groupName), self::EXCEPTION_GROUP_NOT_FOUND);
468 // Init the entries' array
472 $iteratorInstance = $this->listGroups[$groupName]->getIterator();
474 // Rewind the iterator for this round
475 $iteratorInstance->rewind();
477 // Go through all entries
478 while ($iteratorInstance->valid()) {
480 $entryIndex = $iteratorInstance->key();
482 // ... and the final entry which is the stored instance
483 $entry = $this->getEntry($entryIndex);
486 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('LIST: Adding entry ' . $entry . ' ...');
488 // Add it to the list
489 $entries[$iteratorInstance->current()] = $entry;
492 $iteratorInstance->next();
500 * Updates the given entry by hash with given array
502 * @param $hash Hash for this entry
503 * @param $entryArray Array with entry we should update
505 * @throws InvalidListHashException If the solved hash index is invalid
507 public function updateCurrentEntryByHash (string $hash, array $entryArray) {
508 // Is the hash valid?
511 throw new InvalidArgumentException('Parameter "hash" is empty');
512 } elseif (!$this->isHashValid($hash)) {
513 // Throw an exception here, hashIndex is unknown at this point
514 throw new InvalidListHashException(array($this, $hash, -999), self::EXCEPTION_INVALID_HASH);
518 $this->listEntries[$hash] = $entryArray;