]> git.mxchange.org Git - core.git/blob - framework/main/classes/lists/class_BaseList.php
Continued:
[core.git] / framework / main / classes / lists / class_BaseList.php
1 <?php
2 // Own namespace
3 namespace Org\Mxchange\CoreFramework\Lists;
4
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;
11
12 // Import SPL stuff
13 use \BadMethodCallException;
14 use \InvalidArgumentException;
15 use \IteratorAggregate;
16 use \Countable;
17
18 /**
19  * A general list class
20  *
21  * @author              Roland Haeder <webmaster@shipsimu.org>
22  * @version             0.0.0
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
26  *
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.
31  *
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.
36  *
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/>.
39  */
40 abstract class BaseList extends BaseFrameworkSystem implements IteratorAggregate, Countable {
41         // Load traits
42         use IteratorTrait;
43
44         // Exception constants
45         const EXCEPTION_GROUP_ALREADY_ADDED = 0xf20;
46         const EXCEPTION_GROUP_NOT_FOUND     = 0xf21;
47         const EXCEPTION_INVALID_HASH        = 0xf22;
48
49         /**
50          * List groups array
51          */
52         private $listGroups = [];
53
54         /**
55          * List entries array
56          */
57         private $listEntries = [];
58
59         /**
60          * List index array
61          */
62         private $listIndex = [];
63
64         /**
65          * Cached values from "expensive" method calls
66          */
67         private $cache = [
68                 // Cached isValidHash() calls
69                 'is_valid' => [],
70         ];
71
72         /**
73          * Protected constructor
74          *
75          * @param       $className      Name of the class
76          * @return      void
77          */
78         protected function __construct (string $className) {
79                 // Call parent constructor
80                 parent::__construct($className);
81         }
82
83         /**
84          * Getter for iterator instance from this list
85          *
86          * @return      $iteratorInstance       An instance of a Iterator class
87          */
88         public function getIterator () {
89                 // Get iterator from here
90                 $iteratorInstance = $this->getIteratorInstance();
91
92                 // Is the instance set?
93                 if (is_null($iteratorInstance)) {
94                         // Prepare a default iterator
95                         $iteratorInstance = ObjectFactory::createObjectByConfiguredName('default_iterator_class', [$this]);
96
97                         // Set it here
98                         $this->setIteratorInstance($iteratorInstance);
99                 }
100
101                 // And return it
102                 return $iteratorInstance;
103         }
104
105         /**
106          * Checks whether the given group is set
107          *
108          * @param       $groupName      Group to check if found in list
109          * @return      $isset          Whether the group is valid
110          */
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)) {
115                         // Throw IAE
116                         throw new InvalidArgumentException('Parameter "groupName" is empty');
117                 }
118
119                 // Check on existence ...
120                 return isset($this->listGroups[$groupName]);
121         }
122
123         /**
124          * Adds the given group or if already added issues a BadMethodCallException
125          *
126          * @param       $groupName      Group to add
127          * @return      void
128          * @throws      BadMethodCallException  If the given group is already added
129          */
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)) {
134                         // Throw IAE
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);
139                 }
140
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!');
144         }
145
146         /**
147          * Adds the given instance to list group and sub group
148          *
149          * @param       $groupName                      Group to add instance to
150          * @param       $subGroup                       Sub group to add instance to
151          * @param       $visitableInstance      An instance of Visitable
152          * @return      void
153          * @throws      BadMethodCallException  If the given group is not found
154          */
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)) {
159                         // Throw IAE
160                         throw new InvalidArgumentException('Parameter "groupName" is empty');
161                 } elseif (empty($subGroup)) {
162                         // Throw it again
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);
167                 }
168
169                 // Is the sub group there?
170                 if (!$this->listGroups[$groupName]->isGroupSet($subGroup)) {
171                         // Automatically add it
172                         $this->listGroups[$groupName]->addGroup($subGroup);
173                 }
174
175                 // Generate the hash
176                 $hash = $this->generateHash($groupName, $subGroup, $visitableInstance);
177
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);
181
182                 // Add the instance itself to the list
183                 $this->listEntries[$hash] = $visitableInstance;
184
185                 // Debug message
186                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: groupName=' . $groupName  . ',subGroup=' . $subGroup . ' - EXIT!');
187         }
188
189         /**
190          * Gets an array from given list
191          *
192          * @param       $groupName      The requested list
193          * @return      $array  The requested array
194          * @throws      BadMethodCallException  If the given group is not found
195          */
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)) {
200                         // Throw IAE
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);
205                 }
206
207                 // Init array
208                 $array = [];
209
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);
215                 }
216
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)) {
222                                 // Add it
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!');
225                         }
226                 }
227
228                 // Return it
229                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: groupName[' . gettype($groupName) . ']=' . $groupName . ',[]=' . count($array) . ' - EXIT!');
230                 return $array;
231         }
232
233         /**
234          * Adds the given entry to list group
235          *
236          * @param       $groupName      Group to add instance to
237          * @param       $entry          An entry of any type
238          * @return      void
239          * @throws      BadMethodCallException  If the given group is not found
240          */
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)) {
245                         // Throw IAE
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);
250                 }
251
252                 // Generate hash
253                 $hash = $this->generateHash($groupName, $groupName, $entry);
254
255                 // Debug message
256                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: groupName=' . $groupName . ',entry=' . print_r($entry, true) . ', hash=' . $hash);
257
258                 // Add the hash to the index
259                 array_push($this->listIndex, $hash);
260
261                 // Debug message
262                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: groupName=' . $groupName . ',listEntries()=' . count($this->listEntries));
263
264                 // Now add the entry to the list
265                 $this->listEntries[$hash] = $entry;
266
267                 // Debug message
268                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: groupName=' . $groupName . ',listEntries()=' . count($this->listEntries) . ' - EXIT!');
269         }
270
271         /**
272          * Removes given entry from the list group
273          *
274          * @param       $groupName      Group where we should remove the entry from
275          * @param       $entry          The entry we should remove
276          * @return      void
277          * @throws      BadMethodCallException  If the given group is not found
278          */
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)) {
283                         // Throw IAE
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);
288                 }
289
290                 // Generate hash
291                 $hash = $this->generateHash($groupName, $groupName, $entry);
292
293                 // Debug message
294                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: groupName=' . $groupName . ',entry=' . $entry . ', hash=' . $hash);
295
296                 // Remove it from the list ...
297                 unset($this->listEntries[$hash]);
298
299                 // ... and hash list as well
300                 unset($this->listIndex[array_search($hash, $this->listIndex)]);
301
302                 // Debug message
303                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: groupName=' . $groupName . ' - EXIT!');
304         }
305
306         /**
307          * Generates a hash from given group, sub group and entry
308          *
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
313          */
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!');
317                 $entry2 = 'null';
318
319                 // Determine type of entry
320                 if (is_null($entry)) {
321                         // Ignore this
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)) {
329                         // String found
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'))) {
335                         // Call it instead
336                         $entry2 = $this->getCallbackInstance()->generateListHashFromEntry($entry);
337                 } else {
338                         // Unsupported type detected
339                         self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: Entry type ' . gettype($entry) . ' is unsupported (this->callbackInstance=' . $this->getCallbackInstance() . ').');
340
341                         // At least take all keys from array
342                         $entry2 = gettype($entry) . ':' . implode(':', array_keys($entry));
343                 }
344
345                 // Construct string which we shall hash
346                 $hashString = sprintf('%s:%s:%s', $groupName, $subGroup, $entry2);
347
348                 // Hash it with fastest hasher
349                 $hash = crc32($hashString);
350
351                 // And return it
352                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-LIST: hash=%s - EXIT!', $hash));
353                 return $hash;
354         }
355
356         /**
357          * Clears an array of groups, all are being checked for existence
358          *
359          * @param       $groupNames             An array with existing list groups
360          * @return      void
361          */
362         protected function clearGroups (array $groupNames) {
363                 // Walk through all groups
364                 foreach ($groupNames as $groupName) {
365                         // Clear this group
366                         $this->clearGroup($groupName);
367                 }
368         }
369
370         /**
371          * Clears a single group by resetting it to its initial state (empty array)
372          *
373          * @param       $groupName      Name of an existing group to clear
374          * @return      void
375          */
376         protected function clearGroup (string $groupName) {
377                 // Does this group exist?
378                 if (empty($groupName)) {
379                         // Throw IAE
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);
384                 }
385
386                 // Then clear this group list
387                 $this->listGroups[$groupName]->clearList();
388
389                 // Clear this list
390                 $this->listIndex = [];
391                 $this->listEntries = [];
392         }
393         
394         /**
395          * Counts all entries in this list
396          *
397          * @return      $count  All entries in this list
398          */
399         public final function count () {
400                 return count($this->listIndex);
401         }
402
403         /**
404          * Checks whether the given hash is valid
405          *
406          * @param       $hash           The hash we should validate
407          * @return      $isValid        Whether the given hash is valid
408          */
409         public final function isHashValid (string $hash) {
410                 // Validate parameter
411                 if (empty($hash)) {
412                         // Throw IAE
413                         throw new InvalidArgumentException('Parameter "hash" is empty');
414                 } elseif (!isset($this->cache['is_valid'][$hash])) {
415                         // Check it
416                         $this->cache['is_valid'][$hash] = ((in_array($hash, $this->listIndex)) && (isset($this->listEntries[$hash])));
417                 }
418
419                 // Return the result
420                 return $this->cache['is_valid'][$hash];
421         }
422
423         /**
424          * Getter for hash from given hash index
425          *
426          * @param       $hashIndex      Index holding the hash
427          * @return      $hash           The hash
428          */
429         public final function getHashByIndex (int $hashIndex) {
430                 // Get it ...
431                 $hash = $this->listIndex[$hashIndex];
432
433                 // ... and return it
434                 return $hash;
435         }
436
437         /**
438          * Gets an entry from given hash index
439          *
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
443          */
444         public function getEntryByIndex (int $hashIndex) {
445                 // Get the hash value
446                 $hash = $this->getHashByIndex($hashIndex);
447
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);
452                 }
453
454                 // Now copy the entry
455                 $entry = $this->listEntries[$hash];
456
457                 // Return it
458                 return $entry;
459         }
460
461         /**
462          * Gets a full array from given group name
463          *
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
467          */
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)) {
472                         // Throw IAE
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);
477                 }
478
479                 // Init the entries' array
480                 $entries = [];
481
482                 // Get an iterator
483                 $iteratorInstance = $this->listGroups[$groupName]->getIterator();
484
485                 // Rewind the iterator for this round
486                 $iteratorInstance->rewind();
487
488                 // Go through all entries
489                 while ($iteratorInstance->valid()) {
490                         // Get key
491                         $entryIndex = $iteratorInstance->key();
492
493                         // ... and the final entry which is the stored instance
494                         $entry = $this->getEntryByIndex($entryIndex);
495
496                         // Add it to the list
497                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: Adding entry ' . $entry . ' ...');
498                         $entries[$iteratorInstance->current()] = $entry;
499
500                         // Skip to next one
501                         $iteratorInstance->next();
502                 }
503
504                 // Return the list
505                 return $entries;
506         }
507
508         /**
509          * Updates the given entry by hash with given array
510          *
511          * @param       $hash           Hash for this entry
512          * @param       $entryArray     Array with entry we should update
513          * @return      void
514          * @throws      InvalidListHashException        If the solved hash index is invalid
515          */
516         public function updateCurrentEntryByHash (string $hash, array $entryArray) {
517                 // Is the hash valid?
518                 if (empty($hash)) {
519                         // Throw IAE
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);
524                 }
525
526                 // Set the entry
527                 $this->listEntries[$hash] = $entryArray;
528         }
529
530 }