]> git.mxchange.org Git - core.git/blob - framework/main/classes/stacker/file/class_BaseFileStack.php
Continued:
[core.git] / framework / main / classes / stacker / file / class_BaseFileStack.php
1 <?php
2 // Own namespace
3 namespace Org\Mxchange\CoreFramework\Stack\File;
4
5 // Import framework stuff
6 use Org\Mxchange\CoreFramework\Factory\Stack\File\FileStackIndexFactory;
7 use Org\Mxchange\CoreFramework\Factory\Object\ObjectFactory;
8 use Org\Mxchange\CoreFramework\Filesystem\File\BaseBinaryFile;
9 use Org\Mxchange\CoreFramework\Generic\UnsupportedOperationException;
10 use Org\Mxchange\CoreFramework\Stack\BaseStacker;
11 use Org\Mxchange\CoreFramework\Traits\Index\IndexableTrait;
12 use Org\Mxchange\CoreFramework\Traits\Iterator\IteratorTrait;
13 use Org\Mxchange\CoreFramework\Utils\String\StringUtils;
14
15 // Import SPL stuff
16 use \InvalidArgumentException;
17 use \SplFileInfo;
18 use \UnexpectedValueException;
19
20 /**
21  * A general file-based stack class
22  *
23  * @author              Roland Haeder <webmaster@ship-simu.org>
24  * @version             0.0.0
25  * @copyright   Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2021 Core Developer Team
26  * @license             GNU GPL 3.0 or any newer version
27  * @link                http://www.ship-simu.org
28  *
29  * This program is free software: you can redistribute it and/or modify
30  * it under the terms of the GNU General Public License as published by
31  * the Free Software Foundation, either version 3 of the License, or
32  * (at your option) any later version.
33  *
34  * This program is distributed in the hope that it will be useful,
35  * but WITHOUT ANY WARRANTY; without even the implied warranty of
36  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
37  * GNU General Public License for more details.
38  *
39  * You should have received a copy of the GNU General Public License
40  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
41  */
42 abstract class BaseFileStack extends BaseStacker {
43         // Load traits
44         use IndexableTrait;
45         use IteratorTrait;
46
47         // Exception codes
48         const EXCEPTION_BAD_MAGIC = 0xe100;
49
50         /**
51          * Minimum block length
52          */
53         private static $minimumBlockLength = 0;
54
55         /**
56          * Protected constructor
57          *
58          * @param       $className      Name of the class
59          * @return      void
60          */
61         protected function __construct (string $className) {
62                 // Call parent constructor
63                 parent::__construct($className);
64         }
65
66         /**
67          * Reads the file header
68          *
69          * @return      void
70          * @todo        To hard assertions here, better rewrite them to exceptions
71          * @throws      UnexpectedValueException        If header is not proper length
72          * @throws      InvalidMagicException   If a bad magic was found
73          */
74         public function readStackHeader () {
75                 // First rewind to beginning as the header sits at the beginning ...
76                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: CALLED!');
77                 $this->getIteratorInstance()->rewind();
78
79                 // Then read it (see constructor for calculation)
80                 $data = $this->getIteratorInstance()->read($this->getIteratorInstance()->getHeaderSize());
81
82                 // Have all requested bytes been read?
83                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: Read %d bytes (%d wanted).', strlen($data), $this->getIteratorInstance()->getHeaderSize()));
84                 if (strlen($data) != $this->getIteratorInstance()->getHeaderSize()) {
85                         // Bad data length
86                         throw new UnexpectedValueException(sprintf('data(%d)=%s does not match iteratorInstance->headerSize=%d',
87                                 strlen($data),
88                                 $data,
89                                 $this->getIteratorInstance()->getHeaderSize()
90                         ));
91                 } elseif (empty(trim($data, chr(0)))) {
92                         // Empty header, file is freshly pre-allocated
93                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: Empty file header detected - EXIT!');
94                         return;
95                 }
96
97                 // Last character must be the separator
98                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: data(-1)=%s', dechex(ord(substr($data, -1, 1)))));
99                 if (substr($data, -1, 1) !== chr(BaseBinaryFile::SEPARATOR_HEADER_ENTRIES)) {
100                         // Not valid separator
101                         throw new UnexpectedValueException(sprintf('data=%s does not have separator=%s at the end.',
102                                 $data,
103                                 BaseBinaryFile::SEPARATOR_HEADER_ENTRIES
104                         ));
105                 }
106
107                 // Okay, then remove it
108                 $data = substr($data, 0, -1);
109
110                 // And update seek position
111                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: Calling this->iteratorInstance->updateSeekPosition() ...');
112                 $this->getIteratorInstance()->updateSeekPosition();
113
114                 /*
115                  * Now split it:
116                  *
117                  * 0 => magic
118                  * 1 => total entries
119                  * 2 => current seek position
120                  */
121                 $header = explode(chr(BaseBinaryFile::SEPARATOR_HEADER_DATA), $data);
122
123                 // Check if the array has only 3 elements
124                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: header(%d)=%s', count($header), print_r($header, true)));
125                 if (count($header) != 3) {
126                         // Header array count is not expected
127                         throw new UnexpectedValueException(sprintf('data=%s has %d elements, expected 3',
128                                 $data,
129                                 count($header)
130                         ));
131                 } elseif ($header[0] != StackableFile::STACK_MAGIC) {
132                         // Bad magic
133                         throw new InvalidMagicException($data, self::EXCEPTION_BAD_MAGIC);
134                 }
135
136                 // Check length of count and seek position
137                 if (strlen($header[1]) != BaseBinaryFile::LENGTH_COUNT) {
138                         // Count length not valid
139                         throw new UnexpectedValueException(sprintf('header[1](%d)=%s is not expected %d length',
140                                 strlen($header[1]),
141                                 $header[1],
142                                 BaseBinaryFile::LENGTH_COUNT
143                         ));
144                 } elseif (strlen($header[1]) != BaseBinaryFile::LENGTH_POSITION) {
145                         // Position length not valid
146                         throw new UnexpectedValueException(sprintf('header[2](%d)=%s is not expected %d length',
147                                 strlen($header[1]),
148                                 $header[1],
149                                 BaseBinaryFile::LENGTH_POSITION
150                         ));
151                 }
152
153                 // Decode count and seek position
154                 $header[1] = hex2bin($header[1]);
155                 $header[2] = hex2bin($header[2]);
156
157                 // Set header here
158                 $this->getIteratorInstance()->setHeader($header);
159
160                 // Trace message
161                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: EXIT!');
162         }
163
164         /**
165          * Flushes the file header
166          *
167          * @return      void
168          */
169         public function flushFileHeader () {
170                 // Put all informations together
171                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: CALLED!');
172                 $header = sprintf('%s%s%s%s%s%s',
173                         // Magic
174                         StackableFile::STACK_MAGIC,
175
176                         // Separator magic<->count
177                         chr(BaseBinaryFile::SEPARATOR_HEADER_DATA),
178
179                         // Padded total entries
180                         str_pad(StringUtils::dec2hex($this->getIteratorInstance()->getCounter()), BaseBinaryFile::LENGTH_COUNT, '0', STR_PAD_LEFT),
181
182                         // Separator count<->seek position
183                         chr(BaseBinaryFile::SEPARATOR_HEADER_DATA),
184
185                         // Padded seek position
186                         str_pad(StringUtils::dec2hex($this->getIteratorInstance()->getSeekPosition(), 2), BaseBinaryFile::LENGTH_POSITION, '0', STR_PAD_LEFT),
187
188                         // Separator position<->entries
189                         chr(BaseBinaryFile::SEPARATOR_HEADER_ENTRIES)
190                 );
191
192                 // Write it to disk (header is always at seek position 0)
193                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: Calling this->iteratorInstance->writeAtPosition(0, header=%s) ...', $header));
194                 $this->getIteratorInstance()->writeAtPosition(0, $header);
195
196                 // Trace message
197                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: EXIT!');
198         }
199
200         /**
201          * Initializes this file-based stack.
202          *
203          * @param       $fileInfoInstance       An instance of a SplFileInfo class
204          * @param       $type           Type of this stack (e.g. url_source for URL sources)
205          * @return      void
206          * @throws      InvalidArgumentException        If a parameter is invalid
207          * @todo        Currently the stack file is not cached, please implement a memory-handling class and if enough RAM is found, cache the whole stack file.
208          */
209         protected function initFileStack (SplFileInfo $fileInfoInstance, string $type) {
210                 // Validate parameter
211                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: fileInfoInstance[%s]=%s,type=%s - CALLED!', get_class($fileInfoInstance), $fileInfoInstance, $type));
212                 if (empty($type)) {
213                         // Invalid parameter
214                         throw new InvalidArgumentException('Parameter "type" is empty');
215                 }
216
217                 // Get a stack file instance
218                 $stackInstance = ObjectFactory::createObjectByConfiguredName('stack_file_class', array($fileInfoInstance, $this));
219
220                 // Get iterator instance
221                 $iteratorInstance = ObjectFactory::createObjectByConfiguredName('file_iterator_class', array($stackInstance));
222
223                 // Set iterator here
224                 $this->setIteratorInstance($iteratorInstance);
225
226                 // Calculate header size
227                 $headerSize = (
228                         strlen(StackableFile::STACK_MAGIC) +
229                         strlen(chr(BaseBinaryFile::SEPARATOR_HEADER_DATA)) +
230                         BaseBinaryFile::LENGTH_COUNT +
231                         strlen(chr(BaseBinaryFile::SEPARATOR_HEADER_DATA)) +
232                         BaseBinaryFile::LENGTH_POSITION +
233                         strlen(chr(BaseBinaryFile::SEPARATOR_HEADER_ENTRIES))
234                 );
235
236                 // Setting it
237                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: Setting headerSize=%d ...', $headerSize));
238                 $this->getIteratorInstance()->setHeaderSize($headerSize);
239
240                 // Init counters and gaps array
241                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: Calling this->iteratorInstance->initCountersGapsArray() ...');
242                 $this->getIteratorInstance()->initCountersGapsArray();
243
244                 /*
245                  * Get stack index instance. This can be used for faster
246                  * "defragmentation" and startup.
247                  */
248                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: Creating index instance for fileInfoInstance[%s]=%s,type=%s ...', get_class($fileInfoInstance), $fileInfoInstance, $type));
249                 $indexInstance = FileStackIndexFactory::createFileStackIndexInstance($fileInfoInstance, $type);
250
251                 // And set it here
252                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: indexInstance=%s', $indexInstance->__toString()));
253                 $this->setIndexInstance($indexInstance);
254
255                 // Is the file's header initialized?
256                 if (!$this->getIteratorInstance()->isFileHeaderInitialized()) {
257                         // First pre-allocate a bit
258                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: Calling this->iteratorInstance->preAllocateFile(file_stack) ...');
259                         $this->getIteratorInstance()->preAllocateFile('file_stack');
260
261                         // Then create file header
262                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: this->iteratorInstance->createFileHeader() ...');
263                         $this->getIteratorInstance()->createFileHeader();
264                 }
265
266                 // Load the file header
267                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: Calling this->readStackHeader() ...');
268                 $this->readStackHeader();
269
270                 // Is the index loaded correctly and the stack file is just created?
271                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: Calling this->indexInstance->isIndexLoaded() ...');
272                 if (!$this->getIndexInstance()->isIndexLoaded()) {
273                         /*
274                          * Something horrible has happened to the index as it should be
275                          * loaded at this point. The stack's file structure then needs to
276                          * be analyzed and the index rebuild.
277                          */
278                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: Calling this->iteratorInstance->analyzeFileStructure() ...');
279                         $this->getIteratorInstance()->analyzeFileStructure();
280
281                         // Rebuild index from file
282                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: Calling this->iteratorInstance->rebuildIndexFromStack(%s) ...', $this->__toString()));
283                         $this->getIndexInstance()->rebuildIndexFromStack($this);
284                 }
285
286                 // Trace message
287                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: EXIT!');
288         }
289
290         /**
291          * Adds a value to given stack
292          *
293          * @param       $stackerName    Name of the stack
294          * @param       $value                  Value to add to this stacker
295          * @return      void
296          * @throws      FullStackerException    If the stack is full
297          * @throws      InvalidArgumentException        If a parameter is not valid
298          * @throws      InvalidArgumentException        Not all variable types are wanted here
299          */
300         protected function addValueToStack (string $stackerName, $value) {
301                 // Validate parameter
302                 /* PRINTR-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackerName=%s,value[]=%s - CALLED!', $stackerName, gettype($value)));
303                 //* PRINTR-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackerName=%s,value[%s]=%s', $stackerName, gettype($value), print_r($value, true)));
304                 if (empty($stackerName)) {
305                         // No empty stack name
306                         throw new InvalidArgumentException('Parameter "stackerName" is empty');
307                 } elseif ($this->isStackFull($stackerName)) {
308                         // Stacker is full
309                         throw new FullStackerException([$this, $stackerName, $value], self::EXCEPTION_STACKER_IS_FULL);
310                 } elseif (is_resource($value) || is_object($value)) {
311                         // Not wanted type
312                         throw new InvalidArgumentException(sprintf('value[]=%s is not supported', gettype($value)));
313                 }
314
315                 /*
316                  * Now add the value to the file stack which returns gap position, a
317                  * hash and length of the raw data.
318                  */
319                 $data = $this->getIteratorInstance()->writeValueToFile($stackerName, $value);
320
321                 // Add the hash and gap position to the index
322                 //* PRINTR-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: data=%s', print_r($data, true));
323                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: Calling this->indexInstance->addHashedDataToIndex(%s,data()=%d) ...', $stackerName, count($data)));
324                 $this->getIndexInstance()->addHashedDataToIndex($stackerName, $data);
325
326                 // Trace message
327                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: EXIT!');
328         }
329
330         /**
331          * Get last value from named stacker
332          *
333          * @param       $stackerName    Name of the stack
334          * @return      $value                  Value of last added value
335          * @throws      InvalidArgumentException        If a parameter is not valid
336          * @throws      BadMethodCallException  If the stack is empty
337          */
338         protected function getLastValue (string $stackerName) {
339                 // Validate parameter
340                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackerName=%s - CALLED!', $stackerName));
341                 if (empty($stackerName)) {
342                         // No empty stack name
343                         throw new InvalidArgumentException('Parameter "stackerName" is empty');
344                 } elseif ($this->isStackEmpty($stackerName)) {
345                         // Throw an exception
346                         throw new BadMethodCallException([$this, $stackerName], self::EXCEPTION_STACKER_IS_EMPTY);
347                 }
348
349                 // Now get the last value
350                 /* NOISY-DEBUG: */ $this->partialStub('[' . __METHOD__ . ':' . __LINE__ . '] stackerName=' . $stackerName);
351                 $value = NULL;
352
353                 // Return it
354                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: value[]=%s - EXIT!', gettype($value)));
355                 return $value;
356         }
357
358         /**
359          * Get first value from named stacker
360          *
361          * @param       $stackerName    Name of the stack
362          * @return      $value                  Value of last added value
363          * @throws      InvalidArgumentException        If a parameter is not valid
364          * @throws      BadMethodCallException  If the stack is empty
365          */
366         protected function getFirstValue (string $stackerName) {
367                 // Validate parameter
368                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackerName=%s - CALLED!', $stackerName));
369                 if (empty($stackerName)) {
370                         // No empty stack name
371                         throw new InvalidArgumentException('Parameter "stackerName" is empty');
372                 } elseif ($this->isStackEmpty($stackerName)) {
373                         // Throw an exception
374                         throw new BadMethodCallException([$this, $stackerName], self::EXCEPTION_STACKER_IS_EMPTY);
375                 }
376
377                 // Now get the first value
378                 /* NOISY-DEBUG: */ $this->partialStub('[' . __METHOD__ . ':' . __LINE__ . '] stackerName=' . $stackerName);
379                 $value = NULL;
380
381                 // Return it
382                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: value[]=%s - EXIT!', gettype($value)));
383                 return $value;
384         }
385
386         /**
387          * "Pops" last entry from stack
388          *
389          * @param       $stackerName    Name of the stack
390          * @return      $value                  Value "poped" from array
391          * @throws      InvalidArgumentException        If a parameter is not valid
392          * @throws      BadMethodCallException  If the stack is empty
393          */
394         protected function popLast (string $stackerName) {
395                 // Validate parameter
396                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackerName=%s - CALLED!', $stackerName));
397                 if (empty($stackerName)) {
398                         // No empty stack name
399                         throw new InvalidArgumentException('Parameter "stackerName" is empty');
400                 } elseif ($this->isStackEmpty($stackerName)) {
401                         // Throw an exception
402                         throw new BadMethodCallException([$this, $stackerName], self::EXCEPTION_STACKER_IS_EMPTY);
403                 }
404
405                 // Now, remove the last entry, we don't care about the return value here, see elseif() block above
406                 /* NOISY-DEBUG: */ $this->partialStub('[' . __METHOD__ . ':' . __LINE__ . '] stackerName=' . $stackerName);
407                 return NULL;
408         }
409
410         /**
411          * "Pops" first entry from stack
412          *
413          * @param       $stackerName    Name of the stack
414          * @return      $value                  Value "shifted" from array
415          * @throws      InvalidArgumentException        If a parameter is not valid
416          * @throws      BadMethodCallException  If the named stacker is empty
417          */
418         protected function popFirst (string $stackerName) {
419                 // Validate parameter
420                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackerName=%s - CALLED!', $stackerName));
421                 if (empty($stackerName)) {
422                         // No empty stack name
423                         throw new InvalidArgumentException('Parameter "stackerName" is empty');
424                 } elseif ($this->isStackEmpty($stackerName)) {
425                         // Throw an exception
426                         throw new BadMethodCallException([$this, $stackerName], self::EXCEPTION_STACKER_IS_EMPTY);
427                 }
428
429                 // Now, remove the last entry, we don't care about the return value here, see elseif() block above
430                 /* NOISY-DEBUG: */ $this->partialStub('[' . __METHOD__ . ':' . __LINE__ . '] stackerName=' . $stackerName);
431                 return NULL;
432         }
433
434         /**
435          * Checks whether the given stack is full
436          *
437          * @param       $stackerName    Name of the stack
438          * @return      $isFull                 Whether the stack is full
439          * @throws      InvalidArgumentException        If a parameter is not valid
440          */
441         protected function isStackFull (string $stackerName) {
442                 // Validate parameter
443                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackerName=%s - CALLED!', $stackerName));
444                 if (empty($stackerName)) {
445                         // No empty stack name
446                         throw new InvalidArgumentException('Parameter "stackerName" is empty');
447                 }
448
449                 // @TODO Please implement this, returning false
450                 /* NOISY-DEBUG: */ $this->partialStub('[' . __METHOD__ . ':' . __LINE__ . '] stackerName=' . $stackerName);
451                 $isFull = false;
452
453                 // Return result
454                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: isFull=%d - EXIT!', intval($isFull)));
455                 return $isFull;
456         }
457
458         /**
459          * Checks whether the given stack is empty
460          *
461          * @param       $stackerName            Name of the stack
462          * @return      $isEmpty                        Whether the stack is empty
463          * @throws      InvalidArgumentException        If a parameter is not valid
464          * @throws      BadMethodCallException  If given stack is missing
465          */
466         public function isStackEmpty (string $stackerName) {
467                 // Validate parameter
468                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackerName=%s - CALLED!', $stackerName));
469                 if (empty($stackerName)) {
470                         // No empty stack name
471                         throw new InvalidArgumentException('Parameter "stackerName" is empty');
472                 }
473
474                 // So, is the stack empty?
475                 $isEmpty = (($this->getStackCount($stackerName)) == 0);
476
477                 // Return result
478                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: isEmpty=%d - EXIT!', intval($isEmpty)));
479                 return $isEmpty;
480         }
481
482         /**
483          * Checks whether the given stack is initialized (set in array $stackers)
484          *
485          * @param       $stackerName    Name of the stack
486          * @return      $isInitialized  Whether the stack is initialized
487          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
488          */
489         public function isStackInitialized (string $stackerName) {
490                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getPointerInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
491         }
492
493         /**
494          * Determines whether the EOF has been reached
495          *
496          * @return      $isEndOfFileReached             Whether the EOF has been reached
497          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
498          */
499         public function isEndOfFileReached () {
500                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getPointerInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
501         }
502
503         /**
504          * Getter for size of given stack (array count)
505          *
506          * @param       $stackerName    Name of the stack
507          * @return      $count                  Size of stack (array count)
508          */
509         public function getStackCount (string $stackerName) {
510                 // Now, simply return the found count value, this must be up-to-date then!
511                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackerName=%s - CALLED!', $stackerName));
512                 $count = $this->getIteratorInstance()->getCounter();
513
514                 // Return count
515                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: count=%d - EXIT!', $count));
516                 return $count;
517         }
518
519         /**
520          * Calculates minimum length for one entry/block
521          *
522          * @return      $length         Minimum length for one entry/block
523          */
524         public function calculateMinimumBlockLength () {
525                 // Is the value "cached"?
526                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: CALLED!');
527                 if (self::$minimumBlockLength == 0) {
528                         // Calulcate it
529                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: Calculating ...');
530                         self::$minimumBlockLength =
531                                 // Length of entry group
532                                 BaseBinaryFile::LENGTH_GROUP + strlen(chr(BaseBinaryFile::SEPARATOR_GROUP_HASH)) +
533                                 // Hash + value
534                                 self::getHashLength() + strlen(chr(BaseBinaryFile::SEPARATOR_HASH_VALUE)) + 1 +
535                                 // Final separator
536                                 strlen(chr(BaseBinaryFile::SEPARATOR_ENTRIES));
537                 }
538
539                 // Return it
540                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: self::minimumBlockLength=%d - EXIT!', self::$minimumBlockLength));
541                 return self::$minimumBlockLength;
542         }
543
544         /**
545          * Initializes counter for valid entries, arrays for damaged entries and
546          * an array for gap seek positions. If you call this method on your own,
547          * please re-analyze the file structure. So you are better to call
548          * analyzeFileStructure() instead of this method.
549          *
550          * @return      void
551          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
552          */
553         public function initCountersGapsArray () {
554                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getPointerInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
555         }
556
557         /**
558          * Getter for header size
559          *
560          * @return      $totalEntries   Size of file header
561          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
562          */
563         public final function getHeaderSize () {
564                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getPointerInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
565         }
566
567         /**
568          * Setter for header size
569          *
570          * @param       $headerSize             Size of file header
571          * @return      void
572          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
573          */
574         public final function setHeaderSize (int $headerSize) {
575                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getPointerInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
576         }
577
578         /**
579          * Getter for header array
580          *
581          * @return      $totalEntries   Size of file header
582          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
583          */
584         public final function getHeader () {
585                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getPointerInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
586         }
587
588         /**
589          * Setter for header
590          *
591          * @param       $header         Array for a file header
592          * @return      void
593          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
594          */
595         public final function setHeader (array $header) {
596                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getPointerInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
597         }
598
599         /**
600          * Updates seekPosition attribute from file to avoid to much access on file.
601          *
602          * @return      void
603          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
604          */
605         public function updateSeekPosition () {
606                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getPointerInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
607         }
608
609         /**
610          * Getter for total entries
611          *
612          * @return      $totalEntries   Total entries in this file
613          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
614          */
615         public final function getCounter () {
616                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getPointerInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
617         }
618
619         /**
620          * Writes data at given position
621          *
622          * @param       $seekPosition   Seek position
623          * @param       $data                   Data to be written
624          * @param       $flushHeader    Whether to flush the header (default: flush)
625          * @return      void
626          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
627          */
628         public function writeData (int $seekPosition, string $data, bool $flushHeader = true) {
629                 // Not supported
630                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: seekPosition=%s,data[]=%s,flushHeader=%d', $seekPosition, gettype($data), intval($flushHeader)));
631                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getPointerInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
632         }
633
634         /**
635          * Writes at given position by seeking to it.
636          *
637          * @param       $seekPosition   Seek position in file
638          * @param       $dataStream             Data to be written
639          * @return      mixed                   Number of writes bytes or false on error
640          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
641          */
642         public function writeAtPosition (int $seekPosition, string $dataStream) {
643                 // Not supported
644                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: seekPosition=%d,dataStream(%d)=%s - CALLED!', $seekPosition, strlen($dataStream), $dataStream));
645                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getPointerInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
646         }
647
648         /**
649          * Writes given value to the file and returns a hash and gap position for it
650          *
651          * @param       $stackName      Group identifier
652          * @param       $value          Value to be added to the stack
653          * @return      $data           Hash and gap position
654          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
655          */
656         public function writeValueToFile (string $stackName, $value) {
657                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackName=%s,value[%s]=%s', $stackName, gettype($value), print_r($value, true)));
658                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getPointerInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
659         }
660
661         /**
662          * Searches for next suitable gap the given length of data can fit in
663          * including padding bytes.
664          *
665          * @param       $length                 Length of raw data
666          * @return      $seekPosition   Found next gap's seek position
667          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
668          */
669         public function searchNextGap (int $length) {
670                 // Not supported here
671                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: length=%d - CALLED!', $length));
672                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getPointerInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
673         }
674
675         /**
676          * "Getter" for file size
677          *
678          * @return      $fileSize       Size of currently loaded file
679          */
680         public function getFileSize () {
681                 // Call iterator's method
682                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: CALLED!');
683                 $size = $this->getIteratorInstance()->getFileSize();
684
685                 // Return size
686                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: size=%d - EXIT!', $size));
687                 return $size;
688         }
689
690         /**
691          * Writes given raw data to the file and returns a gap position and length
692          *
693          * @param       $stackName      Group identifier
694          * @param       $hash           Hash from encoded value
695          * @param       $encoded        Encoded value to be written to the file
696          * @return      $data           Gap position and length of the raw data
697          */
698         public function writeDataToFreeGap (string $stackName, string $hash, string $encoded) {
699                 // Raw data been written to the file
700                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackName=%s,hash=%s,encoded()=%d - CALLED!', $stackName, $hash, strlen($encoded)));
701                 $rawData = sprintf('%s%s%s%s%s',
702                         $stackName,
703                         BaseBinaryFile::SEPARATOR_GROUP_HASH,
704                         hex2bin($hash),
705                         BaseBinaryFile::SEPARATOR_HASH_VALUE,
706                         $encoded
707                 );
708
709                 // Search for next free gap
710                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackName=%s,hash=%s,rawData()=%d', $stackName, $hash, strlen($rawData)));
711                 $gapPosition = $this->getIteratorInstance()->searchNextGap(strlen($rawData));
712
713                 // Gap position cannot be smaller than header length + 1
714                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: gapPosition=%d', $gapPosition));
715                 if ($gapPosition <= $this->getIteratorInstance()->getHeaderSize()) {
716                         // Improper gap position
717                         throw new UnexpectedValueException(sprintf('gapPosition[%s]=%d is not larger than headerSize=%d',
718                                 gettype($gapPosition),
719                                 $gapPosition,
720                                 $this->getIteratorInstance()->getHeaderSize()
721                         ));
722                 }
723
724                 // Then write the data at that gap
725                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackName=%s,hash=%s,gapPosition=%s', $stackName, $hash, $gapPosition));
726                 $this->getIteratorInstance()->writeData($gapPosition, $rawData);
727
728                 // Return gap position, hash and length of raw data
729                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackName=%s,hash=%s,rawData()=%d - EXIT!', $stackName, $hash, strlen($rawData)));
730                 return [
731                         StackableFile::ARRAY_NAME_GAP_POSITION => $gapPosition,
732                         StackableFile::ARRAY_NAME_HASH         => $hash,
733                         StackableFile::ARRAY_NAME_DATA_LENGTH  => strlen($rawData),
734                 ];
735         }
736
737 }