de9f33433c91be0643dd15f5b88dd486fbe0bf16
[core.git] / framework / main / classes / lists / class_BaseList.php
1 <?php
2 // Own namespace
3 namespace CoreFramework\Lists;
4
5 // Import framework stuff
6 use CoreFramework\Factory\ObjectFactory;
7 use CoreFramework\Generic\FrameworkInterface;
8 use CoreFramework\Object\BaseFrameworkSystem;
9 use CoreFramework\Visitor\Visitable;
10
11 // Import SPL stuff
12 use \IteratorAggregate;
13 use \Countable;
14
15 /**
16  * A general list class
17  *
18  * @author              Roland Haeder <webmaster@shipsimu.org>
19  * @version             0.0.0
20  * @copyright   Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2017 Core Developer Team
21  * @license             GNU GPL 3.0 or any newer version
22  * @link                http://www.shipsimu.org
23  *
24  * This program is free software: you can redistribute it and/or modify
25  * it under the terms of the GNU General Public License as published by
26  * the Free Software Foundation, either version 3 of the License, or
27  * (at your option) any later version.
28  *
29  * This program is distributed in the hope that it will be useful,
30  * but WITHOUT ANY WARRANTY; without even the implied warranty of
31  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
32  * GNU General Public License for more details.
33  *
34  * You should have received a copy of the GNU General Public License
35  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
36  */
37 class BaseList extends BaseFrameworkSystem implements IteratorAggregate, Countable {
38         // Exception constants
39         const EXCEPTION_GROUP_ALREADY_ADDED = 0xf20;
40         const EXCEPTION_GROUP_NOT_FOUND     = 0xf21;
41         const EXCEPTION_INVALID_HASH        = 0xf22;
42
43         /**
44          * Call-back instance
45          */
46         private $callbackInstance = NULL;
47
48         /**
49          * List groups array
50          */
51         private $listGroups = array();
52
53         /**
54          * List entries array
55          */
56         private $listEntries = array();
57
58         /**
59          * List index array
60          */
61         private $listIndex = array();
62
63         /**
64          * Protected constructor
65          *
66          * @param       $className      Name of the class
67          * @return      void
68          */
69         protected function __construct ($className) {
70                 // Call parent constructor
71                 parent::__construct($className);
72         }
73
74         /**
75          * Setter for call-back instance
76          *
77          * @param       $callbackInstance       An instance of a FrameworkInterface class
78          * @return      void
79          */
80         public final function setCallbackInstance (FrameworkInterface $callbackInstance) {
81                 $this->callbackInstance = $callbackInstance;
82         }
83
84         /**
85          * Getter for iterator instance from this list
86          *
87          * @return      $iteratorInstance       An instance of a Iterator class
88          */
89         public function getIterator () {
90                 // Get iterator from here
91                 $iteratorInstance = $this->getIteratorInstance();
92
93                 // Is the instance set?
94                 if (is_null($iteratorInstance)) {
95                         // Prepare a default iterator
96                         $iteratorInstance = ObjectFactory::createObjectByConfiguredName('default_iterator_class', array($this));
97
98                         // Set it here
99                         $this->setIteratorInstance($iteratorInstance);
100                 } // END - if
101
102                 // And return it
103                 return $iteratorInstance;
104         }
105
106         /**
107          * Checks whether the given group is set
108          *
109          * @param       $groupName      Group to check if found in list
110          * @return      $isset          Whether the group is valid
111          */
112         public function isGroupSet ($groupName) {
113                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: this=' . $this->__toString() . ',groupName=' . $groupName);
114                 return isset($this->listGroups[$groupName]);
115         }
116
117         /**
118          * Adds the given group or if already added issues a ListGroupAlreadyAddedException
119          *
120          * @param       $groupName      Group to add
121          * @return      void
122          * @throws      ListGroupAlreadyAddedException  If the given group is already added
123          */
124         public function addGroup ($groupName) {
125                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: this=' . $this->__toString() . ',groupName=' . $groupName . ' - CALLED!');
126                 // Is the group already added?
127                 if ($this->isGroupSet($groupName)) {
128                         // Throw the exception here
129                         throw new ListGroupAlreadyAddedException(array($this, $groupName), self::EXCEPTION_GROUP_ALREADY_ADDED);
130                 } // END - if
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('[' . __METHOD__ . ':' . __LINE__ . ']: 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 ($groupName, $subGroup, Visitable $visitableInstance) {
147                 // Debug message
148                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: this=' . $this->__toString() . ',groupName=' . $groupName  . ',subGroup=' . $subGroup . ',visitableInstance=' . $visitableInstance->__toString() . ' - CALLED!');
149
150                 // Is the group there?
151                 if (!$this->isGroupSet($groupName)) {
152                         // Throw the exception here
153                         throw new NoListGroupException(array($this, $groupName), self::EXCEPTION_GROUP_NOT_FOUND);
154                 } // END - if
155
156                 // Is the sub group there?
157                 if (!$this->listGroups[$groupName]->isGroupSet($subGroup)) {
158                         // Automatically add it
159                         $this->listGroups[$groupName]->addGroup($subGroup);
160                 } // END - if
161
162                 // Generate the hash
163                 $hash = $this->generateHash($groupName, $subGroup, $visitableInstance);
164
165                 // Debug message
166                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: this=' . $this->__toString() . ',groupName=' . $groupName  . ',subGroup=' . $subGroup . ',hash=' . $hash . ' - Calling addEntry() ...');
167
168                 // Now add it to the group list and hash it
169                 $this->listGroups[$groupName]->addEntry($subGroup, $hash);
170
171                 // Debug message
172                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: this=' . $this->__toString() . ',this->listGroups[' . $groupName . ']=' . $this->listGroups[$groupName]->__toString());
173
174                 // Add the hash to the index
175                 array_push($this->listIndex, $hash);
176
177                 // Add the instance itself to the list
178                 $this->listEntries[$hash] = $visitableInstance;
179
180                 // Debug message
181                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: this=' . $this->__toString() . ',groupName=' . $groupName  . ',subGroup=' . $subGroup . ' - EXIT!');
182         }
183
184         /**
185          * Gets an array from given list
186          *
187          * @param       $list   The requested list
188          * @return      $array  The requested array
189          * @throws      NoListGroupException    If the given group is not found
190          */
191         public final function getArrayFromList ($list) {
192                 // Debug message
193                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: this=' . $this->__toString() . ',list[' . gettype($list) . ']=' . $list . ' - CALLED!');
194
195                 // Is the group there?
196                 if ((!is_null($list)) && (!$this->isGroupSet($list))) {
197                         // Throw the exception here
198                         throw new NoListGroupException(array($this, $list), self::EXCEPTION_GROUP_NOT_FOUND);
199                 } // END - if
200
201                 // Init array
202                 $array = array();
203
204                 // Is there another list?
205                 if (!is_null($list)) {
206                         // Then get it as well
207                         $array = $this->listGroups[$list]->getArrayFromList(NULL);
208                 } // END - if
209
210                 // Walk through all entries
211                 foreach ($this->listIndex as $hash) {
212                         // Debug message
213                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: hash=' . $hash);
214
215                         // Is the list entry set?
216                         if ($this->isHashValid($hash)) {
217                                 // Add it
218                                 array_push($array, $this->listEntries[$hash]);
219                                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: hash=' . $hash . ',array(' . count($array) . ')=' . print_r($array, true) . ' - ADDED!');
220                         } // END - if
221                 } // END - foreach
222
223                 // Debug message
224                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: this=' . $this->__toString() . ',list[' . gettype($list) . ']=' . $list . ',array()=' . count($array) . ' - EXIT!');
225
226                 // Return it
227                 return $array;
228         }
229
230         /**
231          * Adds the given entry to list group
232          *
233          * @param       $groupName      Group to add instance to
234          * @param       $entry          An entry of any type
235          * @return      void
236          * @throws      NoListGroupException    If the given group is not found
237          */
238         public function addEntry ($groupName, $entry) {
239                 // Debug message
240                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: this=' . $this->__toString() . ',groupName=' . $groupName . ' - CALLED!');
241
242                 // Is the group already added?
243                 if (!$this->isGroupSet($groupName)) {
244                         // Throw the exception here
245                         throw new NoListGroupException(array($this, $groupName), self::EXCEPTION_GROUP_NOT_FOUND);
246                 } // END - if
247
248                 // Generate hash
249                 $hash = $this->generateHash($groupName, $groupName, $entry);
250
251                 // Debug message
252                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: this=' . $this->__toString() . ',groupName=' . $groupName . ',entry=' . print_r($entry, true) . ', hash=' . $hash);
253
254                 // Add the hash to the index
255                 array_push($this->listIndex, $hash);
256
257                 // Debug message
258                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: this=' . $this->__toString() . ',groupName=' . $groupName . ',listEntries()=' . count($this->listEntries));
259
260                 // Now add the entry to the list
261                 $this->listEntries[$hash] = $entry;
262
263                 // Debug message
264                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: this=' . $this->__toString() . ',groupName=' . $groupName . ',listEntries()=' . count($this->listEntries) . ' - EXIT!');
265         }
266
267         /**
268          * Removes given entry from the list group
269          *
270          * @param       $groupName      Group where we should remove the entry from
271          * @param       $entry          The entry we should remove
272          * @return      void
273          * @throws      NoListGroupException    If the given group is not found
274          */
275         public function removeEntry ($groupName, $entry) {
276                 // Debug message
277                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: this=' . $this->__toString() . ',groupName=' . $groupName . ' - CALLED!');
278
279                 // Is the group already added?
280                 if (!$this->isGroupSet($groupName)) {
281                         // Throw the exception here
282                         throw new NoListGroupException(array($this, $groupName), self::EXCEPTION_GROUP_NOT_FOUND);
283                 } // END - if
284
285                 // Generate hash
286                 $hash = $this->generateHash($groupName, $groupName, $entry);
287
288                 // Debug message
289                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: this=' . $this->__toString() . ',groupName=' . $groupName . ',entry=' . $entry . ', hash=' . $hash);
290
291                 // Remove it from the list ...
292                 unset($this->listEntries[$hash]);
293
294                 // ... and hash list as well
295                 unset($this->listIndex[array_search($hash, $this->listIndex)]);
296
297                 // Debug message
298                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: this=' . $this->__toString() . ',groupName=' . $groupName . ' - EXIT!');
299         }
300
301         /**
302          * Generates a hash from given group, sub group and entry
303          *
304          * @param       $groupName      Group to add instance to
305          * @param       $subGroup       Sub group to add instance to
306          * @param       $entry          An entry of any type
307          * @return      $hash           The generated
308          */
309         private function generateHash ($groupName, $subGroup, $entry) {
310                 // Created entry, 'null' is default
311                 $entry2 = 'null';
312
313                 // Determine type of entry
314                 if (is_null($entry)) {
315                         // Ignore this
316                 } elseif ($entry instanceof FrameworkInterface) {
317                         // Own instance detected
318                         $entry2 = $entry->hashCode();
319                 } elseif ((is_int($entry)) || (is_float($entry)) || (is_resource($entry))) {
320                         // Integer/float/resource detected
321                         $entry2 = gettype($entry) . ':' . $entry;
322                 } elseif (is_string($entry)) {
323                         // String found
324                         $entry2 = crc32($entry) . ':' . strlen($entry);
325                 } elseif ((is_array($entry)) && (isset($entry['id']))) {
326                         // Supported array found
327                         $entry2 = crc32($entry['id']) . ':' . count($entry);
328                 } elseif (($this->callbackInstance instanceof FrameworkInterface) && (method_exists($this->callbackInstance, 'generateListHashFromEntry'))) {
329                         // Call it instead
330                         $entry2 = $this->callbackInstance->generateListHashFromEntry($entry);
331                 } else {
332                         // Unsupported type detected
333                         self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST[' . __METHOD__ . ':' . __LINE__ . ']: Entry type ' . gettype($entry) . ' is unsupported (this->callbackInstance=' . $this->callbackInstance . ').');
334
335                         // At least take all keys from array
336                         $entry2 = gettype($entry) . ':' . implode(':', array_keys($entry));
337                 }
338
339                 // Construct string which we shall hash
340                 $hashString = $groupName . ':' . $subGroup . ':' . $entry2;
341
342                 // Hash it with fastest hasher
343                 $hash = crc32($hashString);
344
345                 // And return it
346                 return $hash;
347         }
348
349         /**
350          * Clears an array of groups, all are being checked for existence
351          *
352          * @param       $groupNames             An array with existing list groups
353          * @return      void
354          */
355         protected function clearGroups (array $groupNames) {
356                 // Walk through all groups
357                 foreach ($groupNames as $groupName) {
358                         // Clear this group
359                         $this->clearGroup($groupName);
360                 } // END - foreach
361         }
362
363         /**
364          * Clears a single group by resetting it to its initial state (empty array)
365          *
366          * @param       $groupName      Name of an existing group to clear
367          * @return      void
368          */
369         protected function clearGroup ($groupName) {
370                 // Does this group exist?
371                 if (!$this->isGroupSet($groupName)) {
372                         // Throw the exception here
373                         throw new NoListGroupException(array($this, $groupName), self::EXCEPTION_GROUP_NOT_FOUND);
374                 } // END - if
375
376                 // Then clear this group list
377                 $this->listGroups[$groupName]->clearList();
378
379                 // Clear this list
380                 $this->listIndex = array();
381                 $this->listEntries = array();
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 ($hash) {
400                 // Check it
401                 $isValid = ((in_array($hash, $this->listIndex)) && (isset($this->listEntries[$hash])));
402
403                 // Return the result
404                 return $isValid;
405         }
406
407         /**
408          * Getter for hash from given hash index
409          *
410          * @param       $hashIndex      Index holding the hash
411          * @return      $hash           The hash
412          */
413         public final function getHash ($hashIndex) {
414                 // Get it ...
415                 $hash = $this->listIndex[$hashIndex];
416
417                 // ... and return it
418                 return $hash;
419         }
420
421         /**
422          * Gets an entry from given hash index
423          *
424          * @param       $hashIndex      The hash index to resolve the mapped entry
425          * @return      $entry          Solved entry from list
426          * @throws      InvalidListHashException        If the solved hash index is invalid
427          */
428         public function getEntry ($hashIndex) {
429                 // Get the hash value
430                 $hash = $this->getHash($hashIndex);
431
432                 // Is the hash valid?
433                 if (!$this->isHashValid($hash)) {
434                         // Throw an exception here
435                         throw new InvalidListHashException(array($this, $hash, $hashIndex), self::EXCEPTION_INVALID_HASH);
436                 } // END - if
437
438                 // Now copy the entry
439                 $entry = $this->listEntries[$hash];
440
441                 // Return it
442                 return $entry;
443         }
444
445         /**
446          * Gets a full array from given group name
447          *
448          * @param       $groupName      The group name to get a list for
449          * @return      $entries        The array with all entries
450          * @throws      NoListGroupException    If the specified group is invalid
451          */
452         public function getArrayFromProtocolInstance ($groupName) {
453                 // Is the group valid?
454                 if (!$this->isGroupSet($groupName)) {
455                         // Throw the exception here
456                         throw new NoListGroupException(array($this, $groupName), self::EXCEPTION_GROUP_NOT_FOUND);
457                 } // END - if
458
459                 // Init the entries' array
460                 $entries = array();
461
462                 // Get an iterator
463                 $iteratorInstance = $this->listGroups[$groupName]->getIterator();
464
465                 // Rewind the iterator for this round
466                 $iteratorInstance->rewind();
467
468                 // Go through all entries
469                 while ($iteratorInstance->valid()) {
470                         // Get key
471                         $entryIndex = $iteratorInstance->key();
472
473                         // ... and the final entry which is the stored instance
474                         $entry = $this->getEntry($entryIndex);
475
476                         // Debug message
477                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('LIST: Adding entry ' . $entry . ' ...');
478
479                         // Add it to the list
480                         $entries[$iteratorInstance->current()] = $entry;
481
482                         // Skip to next one
483                         $iteratorInstance->next();
484                 } // END - while
485
486                 // Return the list
487                 return $entries;
488         }
489
490         /**
491          * Updates the given entry by hash with given array
492          *
493          * @param       $hash           Hash for this entry
494          * @param       $entryArray     Array with entry we should update
495          * @return      void
496          * @throws      InvalidListHashException        If the solved hash index is invalid
497          */
498         public function updateCurrentEntryByHash ($hash, array $entryArray) {
499                 // Is the hash valid?
500                 if (!$this->isHashValid($hash)) {
501                         // Throw an exception here, hashIndex is unknown at this point
502                         throw new InvalidListHashException(array($this, $hash, -999), self::EXCEPTION_INVALID_HASH);
503                 } // END - if
504
505                 // Set the entry
506                 $this->listEntries[$hash] = $entryArray;
507         }
508
509 }