580ba0d8bb52970e4c8680534b082972093267f7
[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                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: this=' . $this->__toString() . ',this->listGroups[' . $groupName . ']=' . $this->listGroups[$groupName]->__toString());
170                 //$this->listGroups[$groupName]->addEntry($subGroup, $hash);
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('[' . __METHOD__ . ':' . __LINE__ . ']: this=' . $this->__toString() . ',groupName=' . $groupName  . ',subGroup=' . $subGroup . ' - EXIT!');
180         }
181
182         /**
183          * Gets an array from given list
184          *
185          * @param       $list   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 ($list) {
190                 // Debug message
191                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: this=' . $this->__toString() . ',list[' . gettype($list) . ']=' . $list . ' - CALLED!');
192
193                 // Is the group there?
194                 if ((!is_null($list)) && (!$this->isGroupSet($list))) {
195                         // Throw the exception here
196                         throw new NoListGroupException(array($this, $list), self::EXCEPTION_GROUP_NOT_FOUND);
197                 } // END - if
198
199                 // Init array
200                 $array = array();
201
202                 // Is there another list?
203                 if (!is_null($list)) {
204                         // Then get it as well
205                         $array = $this->listGroups[$list]->getArrayFromList(NULL);
206                 } // END - if
207
208                 // Walk through all entries
209                 foreach ($this->listIndex as $hash) {
210                         // Debug message
211                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: hash=' . $hash);
212
213                         // Is the list entry set?
214                         if ($this->isHashValid($hash)) {
215                                 // Add it
216                                 array_push($array, $this->listEntries[$hash]);
217                                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: hash=' . $hash . ',array(' . count($array) . ')=' . print_r($array, true) . ' - ADDED!');
218                         } // END - if
219                 } // END - foreach
220
221                 // Debug message
222                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: this=' . $this->__toString() . ',list[' . gettype($list) . ']=' . $list . ',array()=' . count($array) . ' - EXIT!');
223
224                 // Return it
225                 return $array;
226         }
227
228         /**
229          * Adds the given entry to list group
230          *
231          * @param       $groupName      Group to add instance to
232          * @param       $entry          An entry of any type
233          * @return      void
234          * @throws      NoListGroupException    If the given group is not found
235          */
236         public function addEntry ($groupName, $entry) {
237                 // Debug message
238                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: this=' . $this->__toString() . ',groupName=' . $groupName . ' - CALLED!');
239
240                 // Is the group already added?
241                 if (!$this->isGroupSet($groupName)) {
242                         // Throw the exception here
243                         throw new NoListGroupException(array($this, $groupName), self::EXCEPTION_GROUP_NOT_FOUND);
244                 } // END - if
245
246                 // Generate hash
247                 $hash = $this->generateHash($groupName, $groupName, $entry);
248
249                 // Debug message
250                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: this=' . $this->__toString() . ',groupName=' . $groupName . ',entry=' . print_r($entry, true) . ', hash=' . $hash);
251
252                 // Add the hash to the index
253                 array_push($this->listIndex, $hash);
254
255                 // Debug message
256                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: this=' . $this->__toString() . ',groupName=' . $groupName . ',listEntries()=' . count($this->listEntries));
257
258                 // Now add the entry to the list
259                 $this->listEntries[$hash] = $entry;
260
261                 // Debug message
262                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: this=' . $this->__toString() . ',groupName=' . $groupName . ',listEntries()=' . count($this->listEntries) . ' - EXIT!');
263         }
264
265         /**
266          * Removes given entry from the list group
267          *
268          * @param       $groupName      Group where we should remove the entry from
269          * @param       $entry          The entry we should remove
270          * @return      void
271          * @throws      NoListGroupException    If the given group is not found
272          */
273         public function removeEntry ($groupName, $entry) {
274                 // Debug message
275                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: this=' . $this->__toString() . ',groupName=' . $groupName . ' - CALLED!');
276
277                 // Is the group already added?
278                 if (!$this->isGroupSet($groupName)) {
279                         // Throw the exception here
280                         throw new NoListGroupException(array($this, $groupName), self::EXCEPTION_GROUP_NOT_FOUND);
281                 } // END - if
282
283                 // Generate hash
284                 $hash = $this->generateHash($groupName, $groupName, $entry);
285
286                 // Debug message
287                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: this=' . $this->__toString() . ',groupName=' . $groupName . ',entry=' . $entry . ', hash=' . $hash);
288
289                 // Remove it from the list ...
290                 unset($this->listEntries[$hash]);
291
292                 // ... and hash list as well
293                 unset($this->listIndex[array_search($hash, $this->listIndex)]);
294
295                 // Debug message
296                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: this=' . $this->__toString() . ',groupName=' . $groupName . ' - EXIT!');
297         }
298
299         /**
300          * Generates a hash from given group, sub group and entry
301          *
302          * @param       $groupName      Group to add instance to
303          * @param       $subGroup       Sub group to add instance to
304          * @param       $entry          An entry of any type
305          * @return      $hash           The generated
306          */
307         private function generateHash ($groupName, $subGroup, $entry) {
308                 // Created entry, 'null' is default
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->callbackInstance instanceof FrameworkInterface) && (method_exists($this->callbackInstance, 'generateListHashFromEntry'))) {
327                         // Call it instead
328                         $entry2 = $this->callbackInstance->generateListHashFromEntry($entry);
329                 } else {
330                         // Unsupported type detected
331                         self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST[' . __METHOD__ . ':' . __LINE__ . ']: Entry type ' . gettype($entry) . ' is unsupported.');
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 = $groupName . ':' . $subGroup . ':' . $entry2;
339
340                 // Hash it with fastest hasher
341                 $hash = crc32($hashString);
342
343                 // And return it
344                 return $hash;
345         }
346
347         /**
348          * Clears an array of groups, all are being checked for existence
349          *
350          * @param       $groupNames             An array with existing list groups
351          * @return      void
352          */
353         protected function clearGroups (array $groupNames) {
354                 // Walk through all groups
355                 foreach ($groupNames as $groupName) {
356                         // Clear this group
357                         $this->clearGroup($groupName);
358                 } // END - foreach
359         }
360
361         /**
362          * Clears a single group by resetting it to its initial state (empty array)
363          *
364          * @param       $groupName      Name of an existing group to clear
365          * @return      void
366          */
367         protected function clearGroup ($groupName) {
368                 // Does this group exist?
369                 if (!$this->isGroupSet($groupName)) {
370                         // Throw the exception here
371                         throw new NoListGroupException(array($this, $groupName), self::EXCEPTION_GROUP_NOT_FOUND);
372                 } // END - if
373
374                 // Then clear this group list
375                 $this->listGroups[$groupName]->clearList();
376
377                 // Clear this list
378                 $this->listIndex = array();
379                 $this->listEntries = array();
380         }
381         
382         /**
383          * Counts all entries in this list
384          *
385          * @return      $count  All entries in this list
386          */
387         public final function count () {
388                 return count($this->listIndex);
389         }
390
391         /**
392          * Checks whether the given hash is valid
393          *
394          * @param       $hash           The hash we should validate
395          * @return      $isValid        Whether the given hash is valid
396          */
397         public final function isHashValid ($hash) {
398                 // Check it
399                 $isValid = ((in_array($hash, $this->listIndex)) && (isset($this->listEntries[$hash])));
400
401                 // Return the result
402                 return $isValid;
403         }
404
405         /**
406          * Getter for hash from given hash index
407          *
408          * @param       $hashIndex      Index holding the hash
409          * @return      $hash           The hash
410          */
411         public final function getHash ($hashIndex) {
412                 // Get it ...
413                 $hash = $this->listIndex[$hashIndex];
414
415                 // ... and return it
416                 return $hash;
417         }
418
419         /**
420          * Gets an entry from given hash index
421          *
422          * @param       $hashIndex      The hash index to resolve the mapped entry
423          * @return      $entry          Solved entry from list
424          * @throws      InvalidListHashException        If the solved hash index is invalid
425          */
426         public function getEntry ($hashIndex) {
427                 // Get the hash value
428                 $hash = $this->getHash($hashIndex);
429
430                 // Is the hash valid?
431                 if (!$this->isHashValid($hash)) {
432                         // Throw an exception here
433                         throw new InvalidListHashException(array($this, $hash, $hashIndex), self::EXCEPTION_INVALID_HASH);
434                 } // END - if
435
436                 // Now copy the entry
437                 $entry = $this->listEntries[$hash];
438
439                 // Return it
440                 return $entry;
441         }
442
443         /**
444          * Gets a full array from given group name
445          *
446          * @param       $groupName      The group name to get a list for
447          * @return      $entries        The array with all entries
448          * @throws      NoListGroupException    If the specified group is invalid
449          */
450         public function getArrayFromProtocolInstance ($groupName) {
451                 // Is the group valid?
452                 if (!$this->isGroupSet($groupName)) {
453                         // Throw the exception here
454                         throw new NoListGroupException(array($this, $groupName), self::EXCEPTION_GROUP_NOT_FOUND);
455                 } // END - if
456
457                 // Init the entries' array
458                 $entries = array();
459
460                 // Get an iterator
461                 $iteratorInstance = $this->listGroups[$groupName]->getIterator();
462
463                 // Rewind the iterator for this round
464                 $iteratorInstance->rewind();
465
466                 // Go through all entries
467                 while ($iteratorInstance->valid()) {
468                         // Get key
469                         $entryIndex = $iteratorInstance->key();
470
471                         // ... and the final entry which is the stored instance
472                         $entry = $this->getEntry($entryIndex);
473
474                         // Debug message
475                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('LIST: Adding entry ' . $entry . ' ...');
476
477                         // Add it to the list
478                         $entries[$iteratorInstance->current()] = $entry;
479
480                         // Skip to next one
481                         $iteratorInstance->next();
482                 } // END - while
483
484                 // Return the list
485                 return $entries;
486         }
487
488         /**
489          * Updates the given entry by hash with given array
490          *
491          * @param       $hash           Hash for this entry
492          * @param       $entryArray     Array with entry we should update
493          * @return      void
494          * @throws      InvalidListHashException        If the solved hash index is invalid
495          */
496         public function updateCurrentEntryByHash ($hash, array $entryArray) {
497                 // Is the hash valid?
498                 if (!$this->isHashValid($hash)) {
499                         // Throw an exception here, hashIndex is unknown at this point
500                         throw new InvalidListHashException(array($this, $hash, -999), self::EXCEPTION_INVALID_HASH);
501                 } // END - if
502
503                 // Set the entry
504                 $this->listEntries[$hash] = $entryArray;
505         }
506
507 }