3 namespace CoreFramework\Lists;
5 // Import framework stuff
6 use CoreFramework\Factory\ObjectFactory;
7 use CoreFramework\Generic\FrameworkInterface;
8 use CoreFramework\Object\BaseFrameworkSystem;
9 use CoreFramework\Visitor\Visitable;
12 use \IteratorAggregate;
16 * A general list class
18 * @author Roland Haeder <webmaster@shipsimu.org>
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
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.
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.
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/>.
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;
46 private $callbackInstance = NULL;
51 private $listGroups = array();
56 private $listEntries = array();
61 private $listIndex = array();
64 * Protected constructor
66 * @param $className Name of the class
69 protected function __construct ($className) {
70 // Call parent constructor
71 parent::__construct($className);
75 * Setter for call-back instance
77 * @param $callbackInstance An instance of a FrameworkInterface class
80 public final function setCallbackInstance (FrameworkInterface $callbackInstance) {
81 $this->callbackInstance = $callbackInstance;
85 * Getter for iterator instance from this list
87 * @return $iteratorInstance An instance of a Iterator class
89 public function getIterator () {
90 // Get iterator from here
91 $iteratorInstance = $this->getIteratorInstance();
93 // Is the instance set?
94 if (is_null($iteratorInstance)) {
95 // Prepare a default iterator
96 $iteratorInstance = ObjectFactory::createObjectByConfiguredName('default_iterator_class', array($this));
99 $this->setIteratorInstance($iteratorInstance);
103 return $iteratorInstance;
107 * Checks whether the given group is set
109 * @param $groupName Group to check if found in list
110 * @return $isset Whether the group is valid
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]);
118 * Adds the given group or if already added issues a ListGroupAlreadyAddedException
120 * @param $groupName Group to add
122 * @throws ListGroupAlreadyAddedException If the given group is already added
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);
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!');
138 * Adds the given instance to list group and sub group
140 * @param $groupName Group to add instance to
141 * @param $subGroup Sub group to add instance to
142 * @param $visitableInstance An instance of Visitable
144 * @throws NoListGroupException If the given group is not found
146 public function addInstance ($groupName, $subGroup, Visitable $visitableInstance) {
148 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: this=' . $this->__toString() . ',groupName=' . $groupName . ',subGroup=' . $subGroup . ',visitableInstance=' . $visitableInstance->__toString() . ' - CALLED!');
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);
156 // Is the sub group there?
157 if (!$this->listGroups[$groupName]->isGroupSet($subGroup)) {
158 // Automatically add it
159 $this->listGroups[$groupName]->addGroup($subGroup);
163 $hash = $this->generateHash($groupName, $subGroup, $visitableInstance);
166 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: this=' . $this->__toString() . ',groupName=' . $groupName . ',subGroup=' . $subGroup . ',hash=' . $hash . ' - Calling addEntry() ...');
168 // Now add it to the group list and hash it
169 $this->listGroups[$groupName]->addEntry($subGroup, $hash);
172 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: this=' . $this->__toString() . ',this->listGroups[' . $groupName . ']=' . $this->listGroups[$groupName]->__toString());
174 // Add the hash to the index
175 array_push($this->listIndex, $hash);
177 // Add the instance itself to the list
178 $this->listEntries[$hash] = $visitableInstance;
181 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: this=' . $this->__toString() . ',groupName=' . $groupName . ',subGroup=' . $subGroup . ' - EXIT!');
185 * Gets an array from given list
187 * @param $list The requested list
188 * @return $array The requested array
189 * @throws NoListGroupException If the given group is not found
191 public final function getArrayFromList ($list) {
193 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: this=' . $this->__toString() . ',list[' . gettype($list) . ']=' . $list . ' - CALLED!');
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);
204 // Is there another list?
205 if (!is_null($list)) {
206 // Then get it as well
207 $array = $this->listGroups[$list]->getArrayFromList(NULL);
210 // Walk through all entries
211 foreach ($this->listIndex as $hash) {
213 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: hash=' . $hash);
215 // Is the list entry set?
216 if ($this->isHashValid($hash)) {
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!');
224 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: this=' . $this->__toString() . ',list[' . gettype($list) . ']=' . $list . ',array()=' . count($array) . ' - EXIT!');
231 * Adds the given entry to list group
233 * @param $groupName Group to add instance to
234 * @param $entry An entry of any type
236 * @throws NoListGroupException If the given group is not found
238 public function addEntry ($groupName, $entry) {
240 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: this=' . $this->__toString() . ',groupName=' . $groupName . ' - CALLED!');
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);
249 $hash = $this->generateHash($groupName, $groupName, $entry);
252 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: this=' . $this->__toString() . ',groupName=' . $groupName . ',entry=' . print_r($entry, true) . ', hash=' . $hash);
254 // Add the hash to the index
255 array_push($this->listIndex, $hash);
258 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: this=' . $this->__toString() . ',groupName=' . $groupName . ',listEntries()=' . count($this->listEntries));
260 // Now add the entry to the list
261 $this->listEntries[$hash] = $entry;
264 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: this=' . $this->__toString() . ',groupName=' . $groupName . ',listEntries()=' . count($this->listEntries) . ' - EXIT!');
268 * Removes given entry from the list group
270 * @param $groupName Group where we should remove the entry from
271 * @param $entry The entry we should remove
273 * @throws NoListGroupException If the given group is not found
275 public function removeEntry ($groupName, $entry) {
277 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: this=' . $this->__toString() . ',groupName=' . $groupName . ' - CALLED!');
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);
286 $hash = $this->generateHash($groupName, $groupName, $entry);
289 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: this=' . $this->__toString() . ',groupName=' . $groupName . ',entry=' . $entry . ', hash=' . $hash);
291 // Remove it from the list ...
292 unset($this->listEntries[$hash]);
294 // ... and hash list as well
295 unset($this->listIndex[array_search($hash, $this->listIndex)]);
298 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('[' . __METHOD__ . ':' . __LINE__ . ']: this=' . $this->__toString() . ',groupName=' . $groupName . ' - EXIT!');
302 * Generates a hash from given group, sub group and entry
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
309 private function generateHash ($groupName, $subGroup, $entry) {
310 // Created entry, 'null' is default
313 // Determine type of entry
314 if (is_null($entry)) {
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)) {
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'))) {
330 $entry2 = $this->callbackInstance->generateListHashFromEntry($entry);
332 // Unsupported type detected
333 self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST[' . __METHOD__ . ':' . __LINE__ . ']: Entry type ' . gettype($entry) . ' is unsupported (this->callbackInstance=' . $this->callbackInstance . ').');
335 // At least take all keys from array
336 $entry2 = gettype($entry) . ':' . implode(':', array_keys($entry));
339 // Construct string which we shall hash
340 $hashString = $groupName . ':' . $subGroup . ':' . $entry2;
342 // Hash it with fastest hasher
343 $hash = crc32($hashString);
350 * Clears an array of groups, all are being checked for existence
352 * @param $groupNames An array with existing list groups
355 protected function clearGroups (array $groupNames) {
356 // Walk through all groups
357 foreach ($groupNames as $groupName) {
359 $this->clearGroup($groupName);
364 * Clears a single group by resetting it to its initial state (empty array)
366 * @param $groupName Name of an existing group to clear
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);
376 // Then clear this group list
377 $this->listGroups[$groupName]->clearList();
380 $this->listIndex = array();
381 $this->listEntries = array();
385 * Counts all entries in this list
387 * @return $count All entries in this list
389 public final function count () {
390 return count($this->listIndex);
394 * Checks whether the given hash is valid
396 * @param $hash The hash we should validate
397 * @return $isValid Whether the given hash is valid
399 public final function isHashValid ($hash) {
401 $isValid = ((in_array($hash, $this->listIndex)) && (isset($this->listEntries[$hash])));
408 * Getter for hash from given hash index
410 * @param $hashIndex Index holding the hash
411 * @return $hash The hash
413 public final function getHash ($hashIndex) {
415 $hash = $this->listIndex[$hashIndex];
422 * Gets an entry from given hash index
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
428 public function getEntry ($hashIndex) {
429 // Get the hash value
430 $hash = $this->getHash($hashIndex);
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);
438 // Now copy the entry
439 $entry = $this->listEntries[$hash];
446 * Gets a full array from given group name
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
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);
459 // Init the entries' array
463 $iteratorInstance = $this->listGroups[$groupName]->getIterator();
465 // Rewind the iterator for this round
466 $iteratorInstance->rewind();
468 // Go through all entries
469 while ($iteratorInstance->valid()) {
471 $entryIndex = $iteratorInstance->key();
473 // ... and the final entry which is the stored instance
474 $entry = $this->getEntry($entryIndex);
477 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('LIST: Adding entry ' . $entry . ' ...');
479 // Add it to the list
480 $entries[$iteratorInstance->current()] = $entry;
483 $iteratorInstance->next();
491 * Updates the given entry by hash with given array
493 * @param $hash Hash for this entry
494 * @param $entryArray Array with entry we should update
496 * @throws InvalidListHashException If the solved hash index is invalid
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);
506 $this->listEntries[$hash] = $entryArray;