]> 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 \InvalidArgumentException;
14 use \IteratorAggregate;
15 use \Countable;
16
17 /**
18  * A general list class
19  *
20  * @author              Roland Haeder <webmaster@shipsimu.org>
21  * @version             0.0.0
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
25  *
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.
30  *
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.
35  *
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/>.
38  */
39 abstract class BaseList extends BaseFrameworkSystem implements IteratorAggregate, Countable {
40         // Load traits
41         use IteratorTrait;
42
43         // Exception constants
44         const EXCEPTION_GROUP_ALREADY_ADDED = 0xf20;
45         const EXCEPTION_GROUP_NOT_FOUND     = 0xf21;
46         const EXCEPTION_INVALID_HASH        = 0xf22;
47
48         /**
49          * List groups array
50          */
51         private $listGroups = [];
52
53         /**
54          * List entries array
55          */
56         private $listEntries = [];
57
58         /**
59          * List index array
60          */
61         private $listIndex = [];
62
63         /**
64          * Protected constructor
65          *
66          * @param       $className      Name of the class
67          * @return      void
68          */
69         protected function __construct (string $className) {
70                 // Call parent constructor
71                 parent::__construct($className);
72         }
73
74         /**
75          * Getter for iterator instance from this list
76          *
77          * @return      $iteratorInstance       An instance of a Iterator class
78          */
79         public function getIterator () {
80                 // Get iterator from here
81                 $iteratorInstance = $this->getIteratorInstance();
82
83                 // Is the instance set?
84                 if (is_null($iteratorInstance)) {
85                         // Prepare a default iterator
86                         $iteratorInstance = ObjectFactory::createObjectByConfiguredName('default_iterator_class', [$this]);
87
88                         // Set it here
89                         $this->setIteratorInstance($iteratorInstance);
90                 }
91
92                 // And return it
93                 return $iteratorInstance;
94         }
95
96         /**
97          * Checks whether the given group is set
98          *
99          * @param       $groupName      Group to check if found in list
100          * @return      $isset          Whether the group is valid
101          */
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)) {
106                         // Throw IAE
107                         throw new InvalidArgumentException('Parameter "groupName" is empty');
108                 }
109
110                 // Check on existence ...
111                 return isset($this->listGroups[$groupName]);
112         }
113
114         /**
115          * Adds the given group or if already added issues a ListGroupAlreadyAddedException
116          *
117          * @param       $groupName      Group to add
118          * @return      void
119          * @throws      ListGroupAlreadyAddedException  If the given group is already added
120          */
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)) {
125                         // Throw IAE
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);
130                 }
131
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!');
135         }
136
137         /**
138          * Adds the given instance to list group and sub group
139          *
140          * @param       $groupName                      Group to add instance to
141          * @param       $subGroup                       Sub group to add instance to
142          * @param       $visitableInstance      An instance of Visitable
143          * @return      void
144          * @throws      NoListGroupException    If the given group is not found
145          */
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)) {
150                         // Throw IAE
151                         throw new InvalidArgumentException('Parameter "groupName" is empty');
152                 } elseif (empty($subGroup)) {
153                         // Throw it again
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);
158                 }
159
160                 // Is the sub group there?
161                 if (!$this->listGroups[$groupName]->isGroupSet($subGroup)) {
162                         // Automatically add it
163                         $this->listGroups[$groupName]->addGroup($subGroup);
164                 }
165
166                 // Generate the hash
167                 $hash = $this->generateHash($groupName, $subGroup, $visitableInstance);
168
169                 // Debug message
170                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: this=' . $this->__toString() . ',this->listGroups[' . $groupName . ']=' . $this->listGroups[$groupName]->__toString());
171
172                 // Add the hash to the index
173                 array_push($this->listIndex, $hash);
174
175                 // Add the instance itself to the list
176                 $this->listEntries[$hash] = $visitableInstance;
177
178                 // Debug message
179                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: this=' . $this->__toString() . ',groupName=' . $groupName  . ',subGroup=' . $subGroup . ' - EXIT!');
180         }
181
182         /**
183          * Gets an array from given list
184          *
185          * @param       $groupName      The requested list
186          * @return      $array  The requested array
187          * @throws      NoListGroupException    If the given group is not found
188          */
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)) {
193                         // Throw IAE
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);
198                 }
199
200                 // Init array
201                 $array = [];
202
203                 // Is there another list?
204                 if (!is_null($groupName)) {
205                         // Then get it as well
206                         $array = $this->listGroups[$groupName]->getArrayFromList(NULL);
207                 }
208
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)) {
214                                 // Add it
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!');
217                         }
218                 }
219
220                 // Return it
221                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: this=' . $this->__toString() . ',groupName[' . gettype($groupName) . ']=' . $groupName . ',[]=' . count($array) . ' - EXIT!');
222                 return $array;
223         }
224
225         /**
226          * Adds the given entry to list group
227          *
228          * @param       $groupName      Group to add instance to
229          * @param       $entry          An entry of any type
230          * @return      void
231          * @throws      NoListGroupException    If the given group is not found
232          */
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)) {
237                         // Throw IAE
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);
242                 }
243
244                 // Generate hash
245                 $hash = $this->generateHash($groupName, $groupName, $entry);
246
247                 // Debug message
248                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: this=' . $this->__toString() . ',groupName=' . $groupName . ',entry=' . print_r($entry, true) . ', hash=' . $hash);
249
250                 // Add the hash to the index
251                 array_push($this->listIndex, $hash);
252
253                 // Debug message
254                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: this=' . $this->__toString() . ',groupName=' . $groupName . ',listEntries()=' . count($this->listEntries));
255
256                 // Now add the entry to the list
257                 $this->listEntries[$hash] = $entry;
258
259                 // Debug message
260                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: this=' . $this->__toString() . ',groupName=' . $groupName . ',listEntries()=' . count($this->listEntries) . ' - EXIT!');
261         }
262
263         /**
264          * Removes given entry from the list group
265          *
266          * @param       $groupName      Group where we should remove the entry from
267          * @param       $entry          The entry we should remove
268          * @return      void
269          * @throws      NoListGroupException    If the given group is not found
270          */
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)) {
275                         // Throw IAE
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);
280                 }
281
282                 // Generate hash
283                 $hash = $this->generateHash($groupName, $groupName, $entry);
284
285                 // Debug message
286                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: this=' . $this->__toString() . ',groupName=' . $groupName . ',entry=' . $entry . ', hash=' . $hash);
287
288                 // Remove it from the list ...
289                 unset($this->listEntries[$hash]);
290
291                 // ... and hash list as well
292                 unset($this->listIndex[array_search($hash, $this->listIndex)]);
293
294                 // Debug message
295                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: this=' . $this->__toString() . ',groupName=' . $groupName . ' - EXIT!');
296         }
297
298         /**
299          * Generates a hash from given group, sub group and entry
300          *
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
305          */
306         private function generateHash (string $groupName, string $subGroup, $entry) {
307                 // Created entry, 'null' is default
308                 $entry2 = 'null';
309
310                 // Determine type of entry
311                 if (is_null($entry)) {
312                         // Ignore this
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)) {
320                         // String found
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'))) {
326                         // Call it instead
327                         $entry2 = $this->getCallbackInstance()->generateListHashFromEntry($entry);
328                 } else {
329                         // Unsupported type detected
330                         self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: Entry type ' . gettype($entry) . ' is unsupported (this->callbackInstance=' . $this->getCallbackInstance() . ').');
331
332                         // At least take all keys from array
333                         $entry2 = gettype($entry) . ':' . implode(':', array_keys($entry));
334                 }
335
336                 // Construct string which we shall hash
337                 $hashString = $groupName . ':' . $subGroup . ':' . $entry2;
338
339                 // Hash it with fastest hasher
340                 $hash = crc32($hashString);
341
342                 // And return it
343                 return $hash;
344         }
345
346         /**
347          * Clears an array of groups, all are being checked for existence
348          *
349          * @param       $groupNames             An array with existing list groups
350          * @return      void
351          */
352         protected function clearGroups (array $groupNames) {
353                 // Walk through all groups
354                 foreach ($groupNames as $groupName) {
355                         // Clear this group
356                         $this->clearGroup($groupName);
357                 }
358         }
359
360         /**
361          * Clears a single group by resetting it to its initial state (empty array)
362          *
363          * @param       $groupName      Name of an existing group to clear
364          * @return      void
365          */
366         protected function clearGroup (string $groupName) {
367                 // Does this group exist?
368                 if (empty($groupName)) {
369                         // Throw IAE
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);
374                 }
375
376                 // Then clear this group list
377                 $this->listGroups[$groupName]->clearList();
378
379                 // Clear this list
380                 $this->listIndex = [];
381                 $this->listEntries = [];
382         }
383         
384         /**
385          * Counts all entries in this list
386          *
387          * @return      $count  All entries in this list
388          */
389         public final function count () {
390                 return count($this->listIndex);
391         }
392
393         /**
394          * Checks whether the given hash is valid
395          *
396          * @param       $hash           The hash we should validate
397          * @return      $isValid        Whether the given hash is valid
398          */
399         public final function isHashValid (string $hash) {
400                 // Validate parameter
401                 if (empty($hash)) {
402                         // Throw IAE
403                         throw new InvalidArgumentException('Parameter "hash" is empty');
404                 }
405
406                 // Check it
407                 $isValid = ((in_array($hash, $this->listIndex)) && (isset($this->listEntries[$hash])));
408
409                 // Return the result
410                 return $isValid;
411         }
412
413         /**
414          * Getter for hash from given hash index
415          *
416          * @param       $hashIndex      Index holding the hash
417          * @return      $hash           The hash
418          */
419         public final function getHash ($hashIndex) {
420                 // Get it ...
421                 $hash = $this->listIndex[$hashIndex];
422
423                 // ... and return it
424                 return $hash;
425         }
426
427         /**
428          * Gets an entry from given hash index
429          *
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
433          */
434         public function getEntry ($hashIndex) {
435                 // Get the hash value
436                 $hash = $this->getHash($hashIndex);
437
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);
442                 }
443
444                 // Now copy the entry
445                 $entry = $this->listEntries[$hash];
446
447                 // Return it
448                 return $entry;
449         }
450
451         /**
452          * Gets a full array from given group name
453          *
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
457          */
458         public function getArrayFromProtocolInstance (string $groupName) {
459                 // Is the group valid?
460                 if (empty($groupName)) {
461                         // Throw IAE
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);
466                 }
467
468                 // Init the entries' array
469                 $entries = [];
470
471                 // Get an iterator
472                 $iteratorInstance = $this->listGroups[$groupName]->getIterator();
473
474                 // Rewind the iterator for this round
475                 $iteratorInstance->rewind();
476
477                 // Go through all entries
478                 while ($iteratorInstance->valid()) {
479                         // Get key
480                         $entryIndex = $iteratorInstance->key();
481
482                         // ... and the final entry which is the stored instance
483                         $entry = $this->getEntry($entryIndex);
484
485                         // Debug message
486                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('LIST: Adding entry ' . $entry . ' ...');
487
488                         // Add it to the list
489                         $entries[$iteratorInstance->current()] = $entry;
490
491                         // Skip to next one
492                         $iteratorInstance->next();
493                 }
494
495                 // Return the list
496                 return $entries;
497         }
498
499         /**
500          * Updates the given entry by hash with given array
501          *
502          * @param       $hash           Hash for this entry
503          * @param       $entryArray     Array with entry we should update
504          * @return      void
505          * @throws      InvalidListHashException        If the solved hash index is invalid
506          */
507         public function updateCurrentEntryByHash (string $hash, array $entryArray) {
508                 // Is the hash valid?
509                 if (empty($hash)) {
510                         // Throw IAE
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);
515                 }
516
517                 // Set the entry
518                 $this->listEntries[$hash] = $entryArray;
519         }
520
521 }