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