3 namespace Org\Mxchange\CoreFramework\Lists;
5 // Import framework stuff
6 use Org\Mxchange\CoreFramework\Factory\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;
13 use \IteratorAggregate;
17 * A general list class
19 * @author Roland Haeder <webmaster@shipsimu.org>
21 * @copyright Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2020 Core Developer Team
22 * @license GNU GPL 3.0 or any newer version
23 * @link http://www.shipsimu.org
25 * This program is free software: you can redistribute it and/or modify
26 * it under the terms of the GNU General Public License as published by
27 * the Free Software Foundation, either version 3 of the License, or
28 * (at your option) any later version.
30 * This program is distributed in the hope that it will be useful,
31 * but WITHOUT ANY WARRANTY; without even the implied warranty of
32 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
33 * GNU General Public License for more details.
35 * You should have received a copy of the GNU General Public License
36 * along with this program. If not, see <http://www.gnu.org/licenses/>.
38 abstract class BaseList extends BaseFrameworkSystem implements IteratorAggregate, Countable {
42 // Exception constants
43 const EXCEPTION_GROUP_ALREADY_ADDED = 0xf20;
44 const EXCEPTION_GROUP_NOT_FOUND = 0xf21;
45 const EXCEPTION_INVALID_HASH = 0xf22;
50 private $listGroups = [];
55 private $listEntries = [];
60 private $listIndex = [];
63 * Protected constructor
65 * @param $className Name of the class
68 protected function __construct (string $className) {
69 // Call parent constructor
70 parent::__construct($className);
74 * Getter for iterator instance from this list
76 * @return $iteratorInstance An instance of a Iterator class
78 public function getIterator () {
79 // Get iterator from here
80 $iteratorInstance = $this->getIteratorInstance();
82 // Is the instance set?
83 if (is_null($iteratorInstance)) {
84 // Prepare a default iterator
85 $iteratorInstance = ObjectFactory::createObjectByConfiguredName('default_iterator_class', array($this));
88 $this->setIteratorInstance($iteratorInstance);
92 return $iteratorInstance;
96 * Checks whether the given group is set
98 * @param $groupName Group to check if found in list
99 * @return $isset Whether the group is valid
101 public function isGroupSet (string $groupName) {
102 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: this=' . $this->__toString() . ',groupName=' . $groupName);
103 return isset($this->listGroups[$groupName]);
107 * Adds the given group or if already added issues a ListGroupAlreadyAddedException
109 * @param $groupName Group to add
111 * @throws ListGroupAlreadyAddedException If the given group is already added
113 public function addGroup (string $groupName) {
114 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: this=' . $this->__toString() . ',groupName=' . $groupName . ' - CALLED!');
115 // Is the group already added?
116 if ($this->isGroupSet($groupName)) {
117 // Throw the exception here
118 throw new ListGroupAlreadyAddedException(array($this, $groupName), self::EXCEPTION_GROUP_ALREADY_ADDED);
121 // Add the group which is a simple array
122 $this->listGroups[$groupName] = ObjectFactory::createObjectByConfiguredName('list_group_class');
123 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: this=' . $this->__toString() . ',groupName=' . $groupName . ' - EXIT!');
127 * Adds the given instance to list group and sub group
129 * @param $groupName Group to add instance to
130 * @param $subGroup Sub group to add instance to
131 * @param $visitableInstance An instance of Visitable
133 * @throws NoListGroupException If the given group is not found
135 public function addInstance (string $groupName, string $subGroup, Visitable $visitableInstance) {
137 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: this=' . $this->__toString() . ',groupName=' . $groupName . ',subGroup=' . $subGroup . ',visitableInstance=' . $visitableInstance->__toString() . ' - CALLED!');
139 // Is the group there?
140 if (!$this->isGroupSet($groupName)) {
141 // Throw the exception here
142 throw new NoListGroupException(array($this, $groupName), self::EXCEPTION_GROUP_NOT_FOUND);
145 // Is the sub group there?
146 if (!$this->listGroups[$groupName]->isGroupSet($subGroup)) {
147 // Automatically add it
148 $this->listGroups[$groupName]->addGroup($subGroup);
152 $hash = $this->generateHash($groupName, $subGroup, $visitableInstance);
155 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: this=' . $this->__toString() . ',this->listGroups[' . $groupName . ']=' . $this->listGroups[$groupName]->__toString());
157 // Add the hash to the index
158 array_push($this->listIndex, $hash);
160 // Add the instance itself to the list
161 $this->listEntries[$hash] = $visitableInstance;
164 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: this=' . $this->__toString() . ',groupName=' . $groupName . ',subGroup=' . $subGroup . ' - EXIT!');
168 * Gets an array from given list
170 * @param $list The requested list
171 * @return $array The requested array
172 * @throws NoListGroupException If the given group is not found
174 public final function getArrayFromList ($list) {
176 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: this=' . $this->__toString() . ',list[' . gettype($list) . ']=' . $list . ' - CALLED!');
178 // Is the group there?
179 if ((!is_null($list)) && (!$this->isGroupSet($list))) {
180 // Throw the exception here
181 throw new NoListGroupException(array($this, $list), self::EXCEPTION_GROUP_NOT_FOUND);
187 // Is there another list?
188 if (!is_null($list)) {
189 // Then get it as well
190 $array = $this->listGroups[$list]->getArrayFromList(NULL);
193 // Walk through all entries
194 foreach ($this->listIndex as $hash) {
196 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: hash=' . $hash);
198 // Is the list entry set?
199 if ($this->isHashValid($hash)) {
201 array_push($array, $this->listEntries[$hash]);
202 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: hash=' . $hash . ',array(' . count($array) . ')=' . print_r($array, true) . ' - ADDED!');
207 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: this=' . $this->__toString() . ',list[' . gettype($list) . ']=' . $list . ',[]=' . count($array) . ' - EXIT!');
214 * Adds the given entry to list group
216 * @param $groupName Group to add instance to
217 * @param $entry An entry of any type
219 * @throws NoListGroupException If the given group is not found
221 public function addEntry (string $groupName, $entry) {
223 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: this=' . $this->__toString() . ',groupName=' . $groupName . ' - CALLED!');
225 // Is the group already added?
226 if (!$this->isGroupSet($groupName)) {
227 // Throw the exception here
228 throw new NoListGroupException(array($this, $groupName), self::EXCEPTION_GROUP_NOT_FOUND);
232 $hash = $this->generateHash($groupName, $groupName, $entry);
235 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: this=' . $this->__toString() . ',groupName=' . $groupName . ',entry=' . print_r($entry, true) . ', hash=' . $hash);
237 // Add the hash to the index
238 array_push($this->listIndex, $hash);
241 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: this=' . $this->__toString() . ',groupName=' . $groupName . ',listEntries()=' . count($this->listEntries));
243 // Now add the entry to the list
244 $this->listEntries[$hash] = $entry;
247 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: this=' . $this->__toString() . ',groupName=' . $groupName . ',listEntries()=' . count($this->listEntries) . ' - EXIT!');
251 * Removes given entry from the list group
253 * @param $groupName Group where we should remove the entry from
254 * @param $entry The entry we should remove
256 * @throws NoListGroupException If the given group is not found
258 public function removeEntry (string $groupName, $entry) {
260 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: this=' . $this->__toString() . ',groupName=' . $groupName . ' - CALLED!');
262 // Is the group already added?
263 if (!$this->isGroupSet($groupName)) {
264 // Throw the exception here
265 throw new NoListGroupException(array($this, $groupName), self::EXCEPTION_GROUP_NOT_FOUND);
269 $hash = $this->generateHash($groupName, $groupName, $entry);
272 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: this=' . $this->__toString() . ',groupName=' . $groupName . ',entry=' . $entry . ', hash=' . $hash);
274 // Remove it from the list ...
275 unset($this->listEntries[$hash]);
277 // ... and hash list as well
278 unset($this->listIndex[array_search($hash, $this->listIndex)]);
281 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: this=' . $this->__toString() . ',groupName=' . $groupName . ' - EXIT!');
285 * Generates a hash from given group, sub group and entry
287 * @param $groupName Group to add instance to
288 * @param $subGroup Sub group to add instance to
289 * @param $entry An entry of any type
290 * @return $hash The generated
292 private function generateHash (string $groupName, string $subGroup, $entry) {
293 // Created entry, 'null' is default
296 // Determine type of entry
297 if (is_null($entry)) {
299 } elseif ($entry instanceof FrameworkInterface) {
300 // Own instance detected
301 $entry2 = $entry->hashCode();
302 } elseif ((is_int($entry)) || (is_float($entry)) || (is_resource($entry))) {
303 // Integer/float/resource detected
304 $entry2 = gettype($entry) . ':' . $entry;
305 } elseif (is_string($entry)) {
307 $entry2 = crc32($entry) . ':' . strlen($entry);
308 } elseif ((is_array($entry)) && (isset($entry['id']))) {
309 // Supported array found
310 $entry2 = crc32($entry['id']) . ':' . count($entry);
311 } elseif (($this->getCallbackInstance() instanceof FrameworkInterface) && (method_exists($this->getCallbackInstance(), 'generateListHashFromEntry'))) {
313 $entry2 = $this->getCallbackInstance()->generateListHashFromEntry($entry);
315 // Unsupported type detected
316 self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-LIST: Entry type ' . gettype($entry) . ' is unsupported (this->callbackInstance=' . $this->getCallbackInstance() . ').');
318 // At least take all keys from array
319 $entry2 = gettype($entry) . ':' . implode(':', array_keys($entry));
322 // Construct string which we shall hash
323 $hashString = $groupName . ':' . $subGroup . ':' . $entry2;
325 // Hash it with fastest hasher
326 $hash = crc32($hashString);
333 * Clears an array of groups, all are being checked for existence
335 * @param $groupNames An array with existing list groups
338 protected function clearGroups (array $groupNames) {
339 // Walk through all groups
340 foreach ($groupNames as $groupName) {
342 $this->clearGroup($groupName);
347 * Clears a single group by resetting it to its initial state (empty array)
349 * @param $groupName Name of an existing group to clear
352 protected function clearGroup (string $groupName) {
353 // Does this group exist?
354 if (!$this->isGroupSet($groupName)) {
355 // Throw the exception here
356 throw new NoListGroupException(array($this, $groupName), self::EXCEPTION_GROUP_NOT_FOUND);
359 // Then clear this group list
360 $this->listGroups[$groupName]->clearList();
363 $this->listIndex = [];
364 $this->listEntries = [];
368 * Counts all entries in this list
370 * @return $count All entries in this list
372 public final function count () {
373 return count($this->listIndex);
377 * Checks whether the given hash is valid
379 * @param $hash The hash we should validate
380 * @return $isValid Whether the given hash is valid
382 public final function isHashValid ($hash) {
384 $isValid = ((in_array($hash, $this->listIndex)) && (isset($this->listEntries[$hash])));
391 * Getter for hash from given hash index
393 * @param $hashIndex Index holding the hash
394 * @return $hash The hash
396 public final function getHash ($hashIndex) {
398 $hash = $this->listIndex[$hashIndex];
405 * Gets an entry from given hash index
407 * @param $hashIndex The hash index to resolve the mapped entry
408 * @return $entry Solved entry from list
409 * @throws InvalidListHashException If the solved hash index is invalid
411 public function getEntry ($hashIndex) {
412 // Get the hash value
413 $hash = $this->getHash($hashIndex);
415 // Is the hash valid?
416 if (!$this->isHashValid($hash)) {
417 // Throw an exception here
418 throw new InvalidListHashException(array($this, $hash, $hashIndex), self::EXCEPTION_INVALID_HASH);
421 // Now copy the entry
422 $entry = $this->listEntries[$hash];
429 * Gets a full array from given group name
431 * @param $groupName The group name to get a list for
432 * @return $entries The array with all entries
433 * @throws NoListGroupException If the specified group is invalid
435 public function getArrayFromProtocolInstance (string $groupName) {
436 // Is the group valid?
437 if (!$this->isGroupSet($groupName)) {
438 // Throw the exception here
439 throw new NoListGroupException(array($this, $groupName), self::EXCEPTION_GROUP_NOT_FOUND);
442 // Init the entries' array
446 $iteratorInstance = $this->listGroups[$groupName]->getIterator();
448 // Rewind the iterator for this round
449 $iteratorInstance->rewind();
451 // Go through all entries
452 while ($iteratorInstance->valid()) {
454 $entryIndex = $iteratorInstance->key();
456 // ... and the final entry which is the stored instance
457 $entry = $this->getEntry($entryIndex);
460 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('LIST: Adding entry ' . $entry . ' ...');
462 // Add it to the list
463 $entries[$iteratorInstance->current()] = $entry;
466 $iteratorInstance->next();
474 * Updates the given entry by hash with given array
476 * @param $hash Hash for this entry
477 * @param $entryArray Array with entry we should update
479 * @throws InvalidListHashException If the solved hash index is invalid
481 public function updateCurrentEntryByHash ($hash, array $entryArray) {
482 // Is the hash valid?
483 if (!$this->isHashValid($hash)) {
484 // Throw an exception here, hashIndex is unknown at this point
485 throw new InvalidListHashException(array($this, $hash, -999), self::EXCEPTION_INVALID_HASH);
489 $this->listEntries[$hash] = $entryArray;