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 \BadMethodCallException;
14 use \InvalidArgumentException;
15 use \IteratorAggregate;
19 * A general list class
21 * @author Roland Haeder <webmaster@shipsimu.org>
23 * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2021 Core Developer Team
24 * @license GNU GPL 3.0 or any newer version
25 * @link http://www.shipsimu.org
27 * This program is free software: you can redistribute it and/or modify
28 * it under the terms of the GNU General Public License as published by
29 * the Free Software Foundation, either version 3 of the License, or
30 * (at your option) any later version.
32 * This program is distributed in the hope that it will be useful,
33 * but WITHOUT ANY WARRANTY; without even the implied warranty of
34 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
35 * GNU General Public License for more details.
37 * You should have received a copy of the GNU General Public License
38 * along with this program. If not, see <http://www.gnu.org/licenses/>.
40 abstract class BaseList extends BaseFrameworkSystem implements IteratorAggregate, Countable {
44 // Exception constants
45 const EXCEPTION_GROUP_ALREADY_ADDED = 0xf20;
46 const EXCEPTION_GROUP_NOT_FOUND = 0xf21;
47 const EXCEPTION_INVALID_HASH = 0xf22;
52 private $listGroups = [];
57 private $listEntries = [];
62 private $listIndex = [];
65 * Cached values from "expensive" method calls
68 // Cached isValidHash() calls
73 * Protected constructor
75 * @param $className Name of the class
78 protected function __construct (string $className) {
79 // Call parent constructor
80 parent::__construct($className);
84 * Getter for iterator instance from this list
86 * @return $iteratorInstance An instance of a Iterator class
88 public function getIterator () {
89 // Get iterator from here
90 $iteratorInstance = $this->getIteratorInstance();
92 // Is the instance set?
93 if (is_null($iteratorInstance)) {
94 // Prepare a default iterator
95 $iteratorInstance = ObjectFactory::createObjectByConfiguredName('default_iterator_class', [$this]);
98 $this->setIteratorInstance($iteratorInstance);
102 return $iteratorInstance;
106 * Checks whether the given group is set
108 * @param $groupName Group to check if found in list
109 * @return $isset Whether the group is valid
111 public function isGroupSet (string $groupName) {
112 // Validate parameter
113 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-LIST: groupName=%s - CALLED!', $groupName));
114 if (empty($groupName)) {
116 throw new InvalidArgumentException('Parameter "groupName" is empty');
119 // Check on existence ...
120 return isset($this->listGroups[$groupName]);
124 * Adds the given group or if already added issues a BadMethodCallException
126 * @param $groupName Group to add
128 * @throws BadMethodCallException If the given group is already added
130 public function addGroup (string $groupName) {
131 // Validate parameter
132 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-LIST: groupName=%s - CALLED!', $groupName));
133 if (empty($groupName)) {
135 throw new InvalidArgumentException('Parameter "groupName" is empty');
136 } elseif ($this->isGroupSet($groupName)) {
137 // Throw the exception here
138 throw new BadMethodCallException(sprintf('groupName=%s is already set', $groupName), self::EXCEPTION_GROUP_ALREADY_ADDED);
141 // Add the group which is a simple array
142 $this->listGroups[$groupName] = ObjectFactory::createObjectByConfiguredName('list_group_class');
143 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: groupName=' . $groupName . ' - EXIT!');
147 * Adds the given instance to list group and sub group
149 * @param $groupName Group to add instance to
150 * @param $subGroup Sub group to add instance to
151 * @param $visitableInstance An instance of Visitable
153 * @throws BadMethodCallException If the given group is not found
155 public function addInstance (string $groupName, string $subGroup, Visitable $visitableInstance) {
156 // Validate parameter
157 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: groupName=' . $groupName . ',subGroup=' . $subGroup . ',visitableInstance=' . $visitableInstance->__toString() . ' - CALLED!');
158 if (empty($groupName)) {
160 throw new InvalidArgumentException('Parameter "groupName" is empty');
161 } elseif (empty($subGroup)) {
163 throw new InvalidArgumentException('Parameter "subGroup" is empty');
164 } elseif (!$this->isGroupSet($groupName)) {
165 // Throw the exception here
166 throw new BadMethodCallException(sprintf('groupName=%s is not a valid group', $groupName), self::EXCEPTION_GROUP_NOT_FOUND);
169 // Is the sub group there?
170 if (!$this->listGroups[$groupName]->isGroupSet($subGroup)) {
171 // Automatically add it
172 $this->listGroups[$groupName]->addGroup($subGroup);
176 $hash = $this->generateHash($groupName, $subGroup, $visitableInstance);
178 // Add the hash to the index
179 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: this->listGroups[' . $groupName . ']=' . $this->listGroups[$groupName]->__toString());
180 array_push($this->listIndex, $hash);
182 // Add the instance itself to the list
183 $this->listEntries[$hash] = $visitableInstance;
186 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: groupName=' . $groupName . ',subGroup=' . $subGroup . ' - EXIT!');
190 * Gets an array from given list
192 * @param $groupName The requested list
193 * @return $array The requested array
194 * @throws BadMethodCallException If the given group is not found
196 public final function getArrayFromList (string $groupName) {
197 // Is the group there?
198 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: groupName[' . gettype($groupName) . ']=' . $groupName . ' - CALLED!');
199 if (empty($groupName)) {
201 throw new InvalidArgumentException('Parameter "groupName" is empty');
202 } elseif (!$this->isGroupSet($groupName)) {
203 // Throw the exception here
204 throw new BadMethodCallException(sprintf('groupName=%s is not a valid group', $groupName), self::EXCEPTION_GROUP_NOT_FOUND);
210 // Is there another list?
211 if ($this->listGroups[$groupName]->isGroupSet($groupName)) {
212 // Then get it as well
213 //* DEBUG-DIE: */ die(sprintf('[%s:%d]: groupName=%s,this=%s', __METHOD__, __LINE__, $groupName, print_r($this, TRUE)));
214 $array = $this->listGroups[$groupName]->getArrayFromList($groupName);
217 // Walk through all entries
218 foreach ($this->listIndex as $hash) {
219 // Is the list entry set?
220 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: hash=' . $hash);
221 if ($this->isHashValid($hash)) {
223 array_push($array, $this->listEntries[$hash]);
224 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: hash=' . $hash . ',array(' . count($array) . ')=' . print_r($array, true) . ' - ADDED!');
229 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: groupName[' . gettype($groupName) . ']=' . $groupName . ',[]=' . count($array) . ' - EXIT!');
234 * Adds the given entry to list group
236 * @param $groupName Group to add instance to
237 * @param $entry An entry of any type
239 * @throws BadMethodCallException If the given group is not found
241 public function addEntry (string $groupName, $entry) {
242 // Is the group already added?
243 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: groupName=' . $groupName . ' - CALLED!');
244 if (empty($groupName)) {
246 throw new InvalidArgumentException('Parameter "groupName" is empty');
247 } elseif (!$this->isGroupSet($groupName)) {
248 // Throw the exception here
249 throw new BadMethodCallException(sprintf('groupName=%s is not a valid group', $groupName), self::EXCEPTION_GROUP_NOT_FOUND);
253 $hash = $this->generateHash($groupName, $groupName, $entry);
256 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: groupName=' . $groupName . ',entry=' . print_r($entry, true) . ', hash=' . $hash);
258 // Add the hash to the index
259 array_push($this->listIndex, $hash);
262 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: groupName=' . $groupName . ',listEntries()=' . count($this->listEntries));
264 // Now add the entry to the list
265 $this->listEntries[$hash] = $entry;
268 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: groupName=' . $groupName . ',listEntries()=' . count($this->listEntries) . ' - EXIT!');
272 * Removes given entry from the list group
274 * @param $groupName Group where we should remove the entry from
275 * @param $entry The entry we should remove
277 * @throws BadMethodCallException If the given group is not found
279 public function removeEntry (string $groupName, $entry) {
280 // Is the group already added?
281 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: groupName=' . $groupName . ' - CALLED!');
282 if (empty($groupName)) {
284 throw new InvalidArgumentException('Parameter "groupName" is empty');
285 } elseif (!$this->isGroupSet($groupName)) {
286 // Throw the exception here
287 throw new BadMethodCallException(sprintf('groupName=%s is not a valid group', $groupName), self::EXCEPTION_GROUP_NOT_FOUND);
291 $hash = $this->generateHash($groupName, $groupName, $entry);
294 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: groupName=' . $groupName . ',entry=' . $entry . ', hash=' . $hash);
296 // Remove it from the list ...
297 unset($this->listEntries[$hash]);
299 // ... and hash list as well
300 unset($this->listIndex[array_search($hash, $this->listIndex)]);
303 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: groupName=' . $groupName . ' - EXIT!');
307 * Generates a hash from given group, sub group and entry
309 * @param $groupName Group to add instance to
310 * @param $subGroup Sub group to add instance to
311 * @param $entry An entry of any type
312 * @return $hash The generated
314 private function generateHash (string $groupName, string $subGroup, $entry) {
315 // Created entry, 'null' is default
316 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: groupName=' . $groupName . ',subGroup=' . $subGroup . ',entry[]=' . gettype($entry) . ' - CALLED!');
319 // Determine type of entry
320 if (is_null($entry)) {
322 } elseif ($entry instanceof FrameworkInterface) {
323 // Own instance detected
324 $entry2 = $entry->hashCode();
325 } elseif ((is_int($entry)) || (is_float($entry)) || (is_resource($entry))) {
326 // Integer/float/resource detected
327 $entry2 = gettype($entry) . ':' . $entry;
328 } elseif (is_string($entry)) {
330 $entry2 = crc32($entry) . ':' . strlen($entry);
331 } elseif ((is_array($entry)) && (isset($entry['id']))) {
332 // Supported array found
333 $entry2 = crc32($entry['id']) . ':' . count($entry);
334 } elseif (($this->getCallbackInstance() instanceof FrameworkInterface) && (method_exists($this->getCallbackInstance(), 'generateListHashFromEntry'))) {
336 $entry2 = $this->getCallbackInstance()->generateListHashFromEntry($entry);
338 // Unsupported type detected
339 self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: Entry type ' . gettype($entry) . ' is unsupported (this->callbackInstance=' . $this->getCallbackInstance() . ').');
341 // At least take all keys from array
342 $entry2 = gettype($entry) . ':' . implode(':', array_keys($entry));
345 // Construct string which we shall hash
346 $hashString = sprintf('%s:%s:%s', $groupName, $subGroup, $entry2);
348 // Hash it with fastest hasher
349 $hash = crc32($hashString);
352 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-LIST: hash=%s - EXIT!', $hash));
357 * Clears an array of groups, all are being checked for existence
359 * @param $groupNames An array with existing list groups
362 protected function clearGroups (array $groupNames) {
363 // Walk through all groups
364 foreach ($groupNames as $groupName) {
366 $this->clearGroup($groupName);
371 * Clears a single group by resetting it to its initial state (empty array)
373 * @param $groupName Name of an existing group to clear
376 protected function clearGroup (string $groupName) {
377 // Does this group exist?
378 if (empty($groupName)) {
380 throw new InvalidArgumentException('Parameter "groupName" is empty');
381 } elseif (!$this->isGroupSet($groupName)) {
382 // Throw the exception here
383 throw new BadMethodCallException(sprintf('groupName=%s is not a valid group', $groupName), self::EXCEPTION_GROUP_NOT_FOUND);
386 // Then clear this group list
387 $this->listGroups[$groupName]->clearList();
390 $this->listIndex = [];
391 $this->listEntries = [];
395 * Counts all entries in this list
397 * @return $count All entries in this list
399 public final function count () {
400 return count($this->listIndex);
404 * Checks whether the given hash is valid
406 * @param $hash The hash we should validate
407 * @return $isValid Whether the given hash is valid
409 public final function isHashValid (string $hash) {
410 // Validate parameter
413 throw new InvalidArgumentException('Parameter "hash" is empty');
414 } elseif (!isset($this->cache['is_valid'][$hash])) {
416 $this->cache['is_valid'][$hash] = ((in_array($hash, $this->listIndex)) && (isset($this->listEntries[$hash])));
420 return $this->cache['is_valid'][$hash];
424 * Getter for hash from given hash index
426 * @param $hashIndex Index holding the hash
427 * @return $hash The hash
429 public final function getHashByIndex (int $hashIndex) {
431 $hash = $this->listIndex[$hashIndex];
438 * Gets an entry from given hash index
440 * @param $hashIndex The hash index to resolve the mapped entry
441 * @return $entry Solved entry from list
442 * @throws InvalidListHashException If the solved hash index is invalid
444 public function getEntryByIndex (int $hashIndex) {
445 // Get the hash value
446 $hash = $this->getHashByIndex($hashIndex);
448 // Is the hash valid?
449 if (!$this->isHashValid($hash)) {
450 // Throw an exception here
451 throw new InvalidListHashException(array($this, $hash, $hashIndex), self::EXCEPTION_INVALID_HASH);
454 // Now copy the entry
455 $entry = $this->listEntries[$hash];
462 * Gets a full array from given group name
464 * @param $groupName The group name to get a list for
465 * @return $entries The array with all entries
466 * @throws BadMethodCallException If the specified group is invalid
468 public function getArrayFromProtocolInstance (string $groupName) {
469 // Is the group valid?
470 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-LIST: groupName=%s - CALLED!', $groupName));
471 if (empty($groupName)) {
473 throw new InvalidArgumentException('Parameter "groupName" is empty');
474 } elseif (!$this->isGroupSet($groupName)) {
475 // Throw the exception here
476 throw new BadMethodCallException(sprintf('groupName=%s is not a valid group', $groupName), self::EXCEPTION_GROUP_NOT_FOUND);
479 // Init the entries' array
483 $iteratorInstance = $this->listGroups[$groupName]->getIterator();
485 // Rewind the iterator for this round
486 $iteratorInstance->rewind();
488 // Go through all entries
489 while ($iteratorInstance->valid()) {
491 $entryIndex = $iteratorInstance->key();
493 // ... and the final entry which is the stored instance
494 $entry = $this->getEntryByIndex($entryIndex);
496 // Add it to the list
497 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: Adding entry ' . $entry . ' ...');
498 $entries[$iteratorInstance->current()] = $entry;
501 $iteratorInstance->next();
509 * Updates the given entry by hash with given array
511 * @param $hash Hash for this entry
512 * @param $entryArray Array with entry we should update
514 * @throws InvalidListHashException If the solved hash index is invalid
516 public function updateCurrentEntryByHash (string $hash, array $entryArray) {
517 // Is the hash valid?
520 throw new InvalidArgumentException('Parameter "hash" is empty');
521 } elseif (!$this->isHashValid($hash)) {
522 // Throw an exception here, hashIndex is unknown at this point
523 throw new InvalidListHashException(array($this, $hash, -999), self::EXCEPTION_INVALID_HASH);
527 $this->listEntries[$hash] = $entryArray;