]> 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 - 2020 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          * Protected constructor
66          *
67          * @param       $className      Name of the class
68          * @return      void
69          */
70         protected function __construct (string $className) {
71                 // Call parent constructor
72                 parent::__construct($className);
73         }
74
75         /**
76          * Getter for iterator instance from this list
77          *
78          * @return      $iteratorInstance       An instance of a Iterator class
79          */
80         public function getIterator () {
81                 // Get iterator from here
82                 $iteratorInstance = $this->getIteratorInstance();
83
84                 // Is the instance set?
85                 if (is_null($iteratorInstance)) {
86                         // Prepare a default iterator
87                         $iteratorInstance = ObjectFactory::createObjectByConfiguredName('default_iterator_class', [$this]);
88
89                         // Set it here
90                         $this->setIteratorInstance($iteratorInstance);
91                 }
92
93                 // And return it
94                 return $iteratorInstance;
95         }
96
97         /**
98          * Checks whether the given group is set
99          *
100          * @param       $groupName      Group to check if found in list
101          * @return      $isset          Whether the group is valid
102          */
103         public function isGroupSet (string $groupName) {
104                 // Validate parameter
105                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-LIST: groupName=%s - CALLED!', $groupName));
106                 if (empty($groupName)) {
107                         // Throw IAE
108                         throw new InvalidArgumentException('Parameter "groupName" is empty');
109                 }
110
111                 // Check on existence ...
112                 return isset($this->listGroups[$groupName]);
113         }
114
115         /**
116          * Adds the given group or if already added issues a BadMethodCallException
117          *
118          * @param       $groupName      Group to add
119          * @return      void
120          * @throws      BadMethodCallException  If the given group is already added
121          */
122         public function addGroup (string $groupName) {
123                 // Validate parameter
124                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-LIST: groupName=%s - CALLED!', $groupName));
125                 if (empty($groupName)) {
126                         // Throw IAE
127                         throw new InvalidArgumentException('Parameter "groupName" is empty');
128                 } elseif ($this->isGroupSet($groupName)) {
129                         // Throw the exception here
130                         throw new BadMethodCallException(sprintf('groupName=%s is already set', $groupName), self::EXCEPTION_GROUP_ALREADY_ADDED);
131                 }
132
133                 // Add the group which is a simple array
134                 $this->listGroups[$groupName] = ObjectFactory::createObjectByConfiguredName('list_group_class');
135                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: groupName=' . $groupName . ' - EXIT!');
136         }
137
138         /**
139          * Adds the given instance to list group and sub group
140          *
141          * @param       $groupName                      Group to add instance to
142          * @param       $subGroup                       Sub group to add instance to
143          * @param       $visitableInstance      An instance of Visitable
144          * @return      void
145          * @throws      BadMethodCallException  If the given group is not found
146          */
147         public function addInstance (string $groupName, string $subGroup, Visitable $visitableInstance) {
148                 // Validate parameter
149                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: groupName=' . $groupName  . ',subGroup=' . $subGroup . ',visitableInstance=' . $visitableInstance->__toString() . ' - CALLED!');
150                 if (empty($groupName)) {
151                         // Throw IAE
152                         throw new InvalidArgumentException('Parameter "groupName" is empty');
153                 } elseif (empty($subGroup)) {
154                         // Throw it again
155                         throw new InvalidArgumentException('Parameter "subGroup" is empty');
156                 } elseif (!$this->isGroupSet($groupName)) {
157                         // Throw the exception here
158                         throw new BadMethodCallException(sprintf('groupName=%s is not a valid group', $groupName), self::EXCEPTION_GROUP_NOT_FOUND);
159                 }
160
161                 // Is the sub group there?
162                 if (!$this->listGroups[$groupName]->isGroupSet($subGroup)) {
163                         // Automatically add it
164                         $this->listGroups[$groupName]->addGroup($subGroup);
165                 }
166
167                 // Generate the hash
168                 $hash = $this->generateHash($groupName, $subGroup, $visitableInstance);
169
170                 // Add the hash to the index
171                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: this->listGroups[' . $groupName . ']=' . $this->listGroups[$groupName]->__toString());
172                 array_push($this->listIndex, $hash);
173
174                 // Add the instance itself to the list
175                 $this->listEntries[$hash] = $visitableInstance;
176
177                 // Debug message
178                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: groupName=' . $groupName  . ',subGroup=' . $subGroup . ' - EXIT!');
179         }
180
181         /**
182          * Gets an array from given list
183          *
184          * @param       $groupName      The requested list
185          * @return      $array  The requested array
186          * @throws      BadMethodCallException  If the given group is not found
187          */
188         public final function getArrayFromList (string $groupName) {
189                 // Is the group there?
190                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: groupName[' . gettype($groupName) . ']=' . $groupName . ' - CALLED!');
191                 if (empty($groupName)) {
192                         // Throw IAE
193                         throw new InvalidArgumentException('Parameter "groupName" is empty');
194                 } elseif (!$this->isGroupSet($groupName)) {
195                         // Throw the exception here
196                         throw new BadMethodCallException(sprintf('groupName=%s is not a valid group', $groupName), self::EXCEPTION_GROUP_NOT_FOUND);
197                 }
198
199                 // Init array
200                 $array = [];
201
202                 // Is there another list?
203                 if ($this->listGroups[$groupName]->isGroupSet($groupName)) {
204                         // Then get it as well
205                         //* DEBUG-DIE: */ die(sprintf('[%s:%d]: groupName=%s,this=%s', __METHOD__, __LINE__, $groupName, print_r($this, TRUE)));
206                         $array = $this->listGroups[$groupName]->getArrayFromList($groupName);
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: 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      BadMethodCallException  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: 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 BadMethodCallException(sprintf('groupName=%s is not a valid group', $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: 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: 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: 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      BadMethodCallException  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: 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 BadMethodCallException(sprintf('groupName=%s is not a valid group', $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: 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: 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                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: groupName=' . $groupName  . ',subGroup=' . $subGroup . ',entry[]=' . gettype($entry) . ' - CALLED!');
309                 $entry2 = 'null';
310
311                 // Determine type of entry
312                 if (is_null($entry)) {
313                         // Ignore this
314                 } elseif ($entry instanceof FrameworkInterface) {
315                         // Own instance detected
316                         $entry2 = $entry->hashCode();
317                 } elseif ((is_int($entry)) || (is_float($entry)) || (is_resource($entry))) {
318                         // Integer/float/resource detected
319                         $entry2 = gettype($entry) . ':' . $entry;
320                 } elseif (is_string($entry)) {
321                         // String found
322                         $entry2 = crc32($entry) . ':' . strlen($entry);
323                 } elseif ((is_array($entry)) && (isset($entry['id']))) {
324                         // Supported array found
325                         $entry2 = crc32($entry['id']) . ':' . count($entry);
326                 } elseif (($this->getCallbackInstance() instanceof FrameworkInterface) && (method_exists($this->getCallbackInstance(), 'generateListHashFromEntry'))) {
327                         // Call it instead
328                         $entry2 = $this->getCallbackInstance()->generateListHashFromEntry($entry);
329                 } else {
330                         // Unsupported type detected
331                         self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: Entry type ' . gettype($entry) . ' is unsupported (this->callbackInstance=' . $this->getCallbackInstance() . ').');
332
333                         // At least take all keys from array
334                         $entry2 = gettype($entry) . ':' . implode(':', array_keys($entry));
335                 }
336
337                 // Construct string which we shall hash
338                 $hashString = sprintf('%s:%s:%s', $groupName, $subGroup, $entry2);
339
340                 // Hash it with fastest hasher
341                 $hash = crc32($hashString);
342
343                 // And return it
344                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-LIST: hash=%s - EXIT!', $hash));
345                 return $hash;
346         }
347
348         /**
349          * Clears an array of groups, all are being checked for existence
350          *
351          * @param       $groupNames             An array with existing list groups
352          * @return      void
353          */
354         protected function clearGroups (array $groupNames) {
355                 // Walk through all groups
356                 foreach ($groupNames as $groupName) {
357                         // Clear this group
358                         $this->clearGroup($groupName);
359                 }
360         }
361
362         /**
363          * Clears a single group by resetting it to its initial state (empty array)
364          *
365          * @param       $groupName      Name of an existing group to clear
366          * @return      void
367          */
368         protected function clearGroup (string $groupName) {
369                 // Does this group exist?
370                 if (empty($groupName)) {
371                         // Throw IAE
372                         throw new InvalidArgumentException('Parameter "groupName" is empty');
373                 } elseif (!$this->isGroupSet($groupName)) {
374                         // Throw the exception here
375                         throw new BadMethodCallException(sprintf('groupName=%s is not a valid group', $groupName), self::EXCEPTION_GROUP_NOT_FOUND);
376                 }
377
378                 // Then clear this group list
379                 $this->listGroups[$groupName]->clearList();
380
381                 // Clear this list
382                 $this->listIndex = [];
383                 $this->listEntries = [];
384         }
385         
386         /**
387          * Counts all entries in this list
388          *
389          * @return      $count  All entries in this list
390          */
391         public final function count () {
392                 return count($this->listIndex);
393         }
394
395         /**
396          * Checks whether the given hash is valid
397          *
398          * @param       $hash           The hash we should validate
399          * @return      $isValid        Whether the given hash is valid
400          */
401         public final function isHashValid (string $hash) {
402                 // Validate parameter
403                 if (empty($hash)) {
404                         // Throw IAE
405                         throw new InvalidArgumentException('Parameter "hash" is empty');
406                 }
407
408                 // Check it
409                 $isValid = ((in_array($hash, $this->listIndex)) && (isset($this->listEntries[$hash])));
410
411                 // Return the result
412                 return $isValid;
413         }
414
415         /**
416          * Getter for hash from given hash index
417          *
418          * @param       $hashIndex      Index holding the hash
419          * @return      $hash           The hash
420          */
421         public final function getHash ($hashIndex) {
422                 // Get it ...
423                 $hash = $this->listIndex[$hashIndex];
424
425                 // ... and return it
426                 return $hash;
427         }
428
429         /**
430          * Gets an entry from given hash index
431          *
432          * @param       $hashIndex      The hash index to resolve the mapped entry
433          * @return      $entry          Solved entry from list
434          * @throws      InvalidListHashException        If the solved hash index is invalid
435          */
436         public function getEntry ($hashIndex) {
437                 // Get the hash value
438                 $hash = $this->getHash($hashIndex);
439
440                 // Is the hash valid?
441                 if (!$this->isHashValid($hash)) {
442                         // Throw an exception here
443                         throw new InvalidListHashException(array($this, $hash, $hashIndex), self::EXCEPTION_INVALID_HASH);
444                 }
445
446                 // Now copy the entry
447                 $entry = $this->listEntries[$hash];
448
449                 // Return it
450                 return $entry;
451         }
452
453         /**
454          * Gets a full array from given group name
455          *
456          * @param       $groupName      The group name to get a list for
457          * @return      $entries        The array with all entries
458          * @throws      BadMethodCallException  If the specified group is invalid
459          */
460         public function getArrayFromProtocolInstance (string $groupName) {
461                 // Is the group valid?
462                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-LIST: groupName=%s - CALLED!', $groupName));
463                 if (empty($groupName)) {
464                         // Throw IAE
465                         throw new InvalidArgumentException('Parameter "groupName" is empty');
466                 } elseif (!$this->isGroupSet($groupName)) {
467                         // Throw the exception here
468                         throw new BadMethodCallException(sprintf('groupName=%s is not a valid group', $groupName), self::EXCEPTION_GROUP_NOT_FOUND);
469                 }
470
471                 // Init the entries' array
472                 $entries = [];
473
474                 // Get an iterator
475                 $iteratorInstance = $this->listGroups[$groupName]->getIterator();
476
477                 // Rewind the iterator for this round
478                 $iteratorInstance->rewind();
479
480                 // Go through all entries
481                 while ($iteratorInstance->valid()) {
482                         // Get key
483                         $entryIndex = $iteratorInstance->key();
484
485                         // ... and the final entry which is the stored instance
486                         $entry = $this->getEntry($entryIndex);
487
488                         // Add it to the list
489                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: Adding entry ' . $entry . ' ...');
490                         $entries[$iteratorInstance->current()] = $entry;
491
492                         // Skip to next one
493                         $iteratorInstance->next();
494                 }
495
496                 // Return the list
497                 return $entries;
498         }
499
500         /**
501          * Updates the given entry by hash with given array
502          *
503          * @param       $hash           Hash for this entry
504          * @param       $entryArray     Array with entry we should update
505          * @return      void
506          * @throws      InvalidListHashException        If the solved hash index is invalid
507          */
508         public function updateCurrentEntryByHash (string $hash, array $entryArray) {
509                 // Is the hash valid?
510                 if (empty($hash)) {
511                         // Throw IAE
512                         throw new InvalidArgumentException('Parameter "hash" is empty');
513                 } elseif (!$this->isHashValid($hash)) {
514                         // Throw an exception here, hashIndex is unknown at this point
515                         throw new InvalidListHashException(array($this, $hash, -999), self::EXCEPTION_INVALID_HASH);
516                 }
517
518                 // Set the entry
519                 $this->listEntries[$hash] = $entryArray;
520         }
521
522 }