]> git.mxchange.org Git - core.git/blob - framework/main/classes/stacker/file/class_BaseFileStack.php
0a2ca5ce2f4aab2176823f43ead80463cfaa27c8
[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\Arrays\ArrayUtils;
15 use Org\Mxchange\CoreFramework\Utils\Strings\StringUtils;
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_STACK_ELEMENT_COUNT=%d,header(%d)=%s', BinaryFile::HEADER_STACK_ELEMENT_COUNT, count($header), print_r($header, true)));
139                 if (count($header) != BinaryFile::HEADER_STACK_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_STACK_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                 } elseif (strlen($header[BinaryFile::HEADER_NAME_TOTAL_ENTRIES]) != BinaryFile::LENGTH_COUNT) {
150                         // Count length not valid
151                         throw new UnexpectedValueException(sprintf('header[%s](%d)=%s is not expected %d length',
152                                 BinaryFile::HEADER_NAME_TOTAL_ENTRIES,
153                                 strlen($header[BinaryFile::HEADER_NAME_TOTAL_ENTRIES]),
154                                 $header[BinaryFile::HEADER_NAME_TOTAL_ENTRIES],
155                                 BinaryFile::LENGTH_COUNT
156                         ));
157                 } elseif (strlen($header[BinaryFile::HEADER_NAME_SEEK_POSITION]) != BinaryFile::LENGTH_POSITION) {
158                         // Position length not valid
159                         throw new UnexpectedValueException(sprintf('header[%s](%d)=%s is not expected %d length',
160                                 BinaryFile::HEADER_NAME_SEEK_POSITION,
161                                 strlen($header[BinaryFile::HEADER_NAME_SEEK_POSITION]),
162                                 $header[BinaryFile::HEADER_NAME_SEEK_POSITION],
163                                 BinaryFile::LENGTH_POSITION
164                         ));
165                 }
166
167                 // Decode count and seek position
168                 $header[BinaryFile::HEADER_NAME_TOTAL_ENTRIES] = hex2bin($header[BinaryFile::HEADER_NAME_TOTAL_ENTRIES]);
169                 $header[BinaryFile::HEADER_NAME_SEEK_POSITION] = hex2bin($header[BinaryFile::HEADER_NAME_SEEK_POSITION]);
170
171                 // Set header here
172                 $this->getIteratorInstance()->getBinaryFileInstance()->setHeader($header);
173
174                 // Trace message
175                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: EXIT!');
176         }
177
178         /**
179          * Flushes the file header
180          *
181          * @return      void
182          */
183         public function flushFileHeader () {
184                 // Put all informations together
185                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: CALLED!');
186                 $header = sprintf('%s%s%s%s%s%s',
187                         // Magic
188                         StackableFile::STACK_MAGIC,
189
190                         // Separator magic<->count
191                         chr(BinaryFile::SEPARATOR_HEADER_DATA),
192
193                         // Padded total entries
194                         str_pad(StringUtils::dec2hex($this->getIteratorInstance()->getBinaryFileInstance()->getCounter()), BinaryFile::LENGTH_COUNT, '0', STR_PAD_LEFT),
195
196                         // Separator count<->seek position
197                         chr(BinaryFile::SEPARATOR_HEADER_DATA),
198
199                         // Padded seek position
200                         str_pad(StringUtils::dec2hex($this->getIteratorInstance()->getBinaryFileInstance()->getSeekPosition(), 2), BinaryFile::LENGTH_POSITION, '0', STR_PAD_LEFT),
201
202                         // Separator position<->entries
203                         chr(BinaryFile::SEPARATOR_HEADER_ENTRIES)
204                 );
205
206                 // Write it to disk (header is always at seek position 0)
207                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: Calling this->iteratorInstance->writeAtPosition(0, header=%s) ...', $header));
208                 $this->getIteratorInstance()->getBinaryFileInstance()->writeAtPosition(0, $header);
209
210                 // Trace message
211                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: EXIT!');
212         }
213
214         /**
215          * Initializes this file-based stack.
216          *
217          * @param       $fileInfoInstance       An instance of a SplFileInfo class
218          * @param       $type           Type of this stack (e.g. url_source for URL sources)
219          * @return      void
220          * @throws      InvalidArgumentException        If a parameter is invalid
221          * @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.
222          */
223         protected function initFileStack (SplFileInfo $fileInfoInstance, string $type) {
224                 // Validate parameter
225                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: fileInfoInstance[%s]=%s,type=%s - CALLED!', get_class($fileInfoInstance), $fileInfoInstance, $type));
226                 if (empty($type)) {
227                         // Invalid parameter
228                         throw new InvalidArgumentException('Parameter "type" is empty');
229                 }
230
231                 // Get a stack file instance
232                 $stackInstance = ObjectFactory::createObjectByConfiguredName('stack_file_class', array($fileInfoInstance, $this));
233
234                 // Get iterator instance
235                 $iteratorInstance = ObjectFactory::createObjectByConfiguredName('file_iterator_class', array($stackInstance));
236
237                 // Set iterator here
238                 $this->setIteratorInstance($iteratorInstance);
239
240                 // Calculate header size
241                 $headerSize = (
242                         strlen(StackableFile::STACK_MAGIC) +
243                         strlen(chr(BinaryFile::SEPARATOR_HEADER_DATA)) +
244                         BinaryFile::LENGTH_COUNT +
245                         strlen(chr(BinaryFile::SEPARATOR_HEADER_DATA)) +
246                         BinaryFile::LENGTH_POSITION +
247                         strlen(chr(BinaryFile::SEPARATOR_HEADER_ENTRIES))
248                 );
249
250                 // Setting it
251                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: Setting headerSize=%d ...', $headerSize));
252                 $this->getIteratorInstance()->getBinaryFileInstance()->setHeaderSize($headerSize);
253
254                 // Init counters and gaps array
255                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: Calling this->iteratorInstance->initCountersGapsArray() ...');
256                 $this->getIteratorInstance()->getBinaryFileInstance()->initCountersGapsArray();
257
258                 /*
259                  * Get stack index instance. This can be used for faster
260                  * "defragmentation" and startup.
261                  */
262                 /* 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));
263                 $indexInstance = FileStackIndexFactory::createFileStackIndexInstance($fileInfoInstance, $type);
264
265                 // And set it here
266                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: indexInstance=%s', $indexInstance->__toString()));
267                 $this->setIndexInstance($indexInstance);
268
269                 // Is the file's header initialized?
270                 if (!$this->getIteratorInstance()->getBinaryFileInstance()->isFileHeaderInitialized()) {
271                         // First pre-allocate a bit
272                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: Calling this->iteratorInstance->preAllocateFile(file_stack) ...');
273                         $this->getIteratorInstance()->getBinaryFileInstance()->preAllocateFile('file_stack');
274
275                         // Then create file header
276                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: this->iteratorInstance->createFileHeader() ...');
277                         $this->getIteratorInstance()->getBinaryFileInstance()->createFileHeader();
278                 }
279
280                 // Load the file header
281                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: Calling this->readStackHeader() ...');
282                 $this->readStackHeader();
283
284                 // Is the index loaded correctly and the stack file is just created?
285                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: Calling this->indexInstance->isIndexLoaded() ...');
286                 if (!$this->getIndexInstance()->isIndexLoaded()) {
287                         /*
288                          * Something horrible has happened to the index as it should be
289                          * loaded at this point. The stack's file structure then needs to
290                          * be analyzed and the index rebuild.
291                          */
292                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: Calling this->iteratorInstance->analyzeFileStructure() ...');
293                         $this->getIteratorInstance()->getBinaryFileInstance()->analyzeFileStructure();
294
295                         // Rebuild index from file
296                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: Calling this->iteratorInstance->rebuildIndexFromStack(%s) ...', $this->__toString()));
297                         $this->getIndexInstance()->rebuildIndexFromStack($this);
298                 }
299
300                 // Trace message
301                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: EXIT!');
302         }
303
304         /**
305          * Adds a value to given stack
306          *
307          * @param       $stackerName    Name of the stack
308          * @param       $value                  Value to add to this stacker
309          * @return      void
310          * @throws      FullStackerException    If the stack is full
311          * @throws      InvalidArgumentException        If a parameter is not valid
312          * @throws      InvalidArgumentException        Not all variable types are wanted here
313          */
314         protected function addValueToStack (string $stackerName, $value) {
315                 // Validate parameter
316                 /* PRINTR-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackerName=%s,value[]=%s - CALLED!', $stackerName, gettype($value)));
317                 //* PRINTR-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackerName=%s,value[%s]=%s', $stackerName, gettype($value), print_r($value, true)));
318                 if (empty($stackerName)) {
319                         // No empty stack name
320                         throw new InvalidArgumentException('Parameter "stackerName" is empty');
321                 } elseif ($this->isStackFull($stackerName)) {
322                         // Stacker is full
323                         throw new FullStackerException([$this, $stackerName, $value], self::EXCEPTION_STACKER_IS_FULL);
324                 } elseif (is_resource($value) || is_object($value)) {
325                         // Not wanted type
326                         throw new InvalidArgumentException(sprintf('value[]=%s is not supported', gettype($value)));
327                 }
328
329                 /*
330                  * Now add the value to the file stack which returns gap position, a
331                  * hash and length of the raw data.
332                  */
333                 $data = $this->getIteratorInstance()->getBinaryFileInstance()->writeValueToFile($stackerName, $value);
334
335                 // Add the hash and gap position to the index
336                 //* PRINTR-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: data=%s', print_r($data, true));
337                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: Calling this->indexInstance->addHashedDataToIndex(%s,data()=%d) ...', $stackerName, count($data)));
338                 $this->getIndexInstance()->addHashedDataToIndex($stackerName, $data);
339
340                 // Trace message
341                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: EXIT!');
342         }
343
344         /**
345          * Get last value from named stacker
346          *
347          * @param       $stackerName    Name of the stack
348          * @return      $value                  Value of last added value
349          * @throws      InvalidArgumentException        If a parameter is not valid
350          * @throws      BadMethodCallException  If the stack is empty
351          */
352         protected function getLastValue (string $stackerName) {
353                 // Validate parameter
354                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackerName=%s - CALLED!', $stackerName));
355                 if (empty($stackerName)) {
356                         // No empty stack name
357                         throw new InvalidArgumentException('Parameter "stackerName" is empty');
358                 } elseif ($this->isStackEmpty($stackerName)) {
359                         // Throw an exception
360                         throw new BadMethodCallException([$this, $stackerName], self::EXCEPTION_STACKER_IS_EMPTY);
361                 }
362
363                 // Now get the last value
364                 /* NOISY-DEBUG: */ $this->partialStub('[' . __METHOD__ . ':' . __LINE__ . '] stackerName=' . $stackerName);
365                 $value = NULL;
366
367                 // Return it
368                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: value[]=%s - EXIT!', gettype($value)));
369                 return $value;
370         }
371
372         /**
373          * Get first value from named stacker
374          *
375          * @param       $stackerName    Name of the stack
376          * @return      $value                  Value of last added value
377          * @throws      InvalidArgumentException        If a parameter is not valid
378          * @throws      BadMethodCallException  If the stack is empty
379          */
380         protected function getFirstValue (string $stackerName) {
381                 // Validate parameter
382                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackerName=%s - CALLED!', $stackerName));
383                 if (empty($stackerName)) {
384                         // No empty stack name
385                         throw new InvalidArgumentException('Parameter "stackerName" is empty');
386                 } elseif ($this->isStackEmpty($stackerName)) {
387                         // Throw an exception
388                         throw new BadMethodCallException([$this, $stackerName], self::EXCEPTION_STACKER_IS_EMPTY);
389                 }
390
391                 // Now get the first value
392                 /* NOISY-DEBUG: */ $this->partialStub('[' . __METHOD__ . ':' . __LINE__ . '] stackerName=' . $stackerName);
393                 $value = NULL;
394
395                 // Return it
396                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: value[]=%s - EXIT!', gettype($value)));
397                 return $value;
398         }
399
400         /**
401          * "Pops" last entry from stack
402          *
403          * @param       $stackerName    Name of the stack
404          * @return      $value                  Value "poped" from array
405          * @throws      InvalidArgumentException        If a parameter is not valid
406          * @throws      BadMethodCallException  If the stack is empty
407          */
408         protected function popLast (string $stackerName) {
409                 // Validate parameter
410                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackerName=%s - CALLED!', $stackerName));
411                 if (empty($stackerName)) {
412                         // No empty stack name
413                         throw new InvalidArgumentException('Parameter "stackerName" is empty');
414                 } elseif ($this->isStackEmpty($stackerName)) {
415                         // Throw an exception
416                         throw new BadMethodCallException([$this, $stackerName], self::EXCEPTION_STACKER_IS_EMPTY);
417                 }
418
419                 // Now, remove the last entry, we don't care about the return value here, see elseif() block above
420                 /* NOISY-DEBUG: */ $this->partialStub('[' . __METHOD__ . ':' . __LINE__ . '] stackerName=' . $stackerName);
421                 return NULL;
422         }
423
424         /**
425          * "Pops" first entry from stack
426          *
427          * @param       $stackerName    Name of the stack
428          * @return      $value                  Value "shifted" from array
429          * @throws      InvalidArgumentException        If a parameter is not valid
430          * @throws      BadMethodCallException  If the named stacker is empty
431          */
432         protected function popFirst (string $stackerName) {
433                 // Validate parameter
434                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackerName=%s - CALLED!', $stackerName));
435                 if (empty($stackerName)) {
436                         // No empty stack name
437                         throw new InvalidArgumentException('Parameter "stackerName" is empty');
438                 } elseif ($this->isStackEmpty($stackerName)) {
439                         // Throw an exception
440                         throw new BadMethodCallException([$this, $stackerName], self::EXCEPTION_STACKER_IS_EMPTY);
441                 }
442
443                 // Now, remove the last entry, we don't care about the return value here, see elseif() block above
444                 /* NOISY-DEBUG: */ $this->partialStub('[' . __METHOD__ . ':' . __LINE__ . '] stackerName=' . $stackerName);
445                 return NULL;
446         }
447
448         /**
449          * Checks whether the given stack is full
450          *
451          * @param       $stackerName    Name of the stack
452          * @return      $isFull                 Whether the stack is full
453          * @throws      InvalidArgumentException        If a parameter is not valid
454          */
455         protected function isStackFull (string $stackerName) {
456                 // Validate parameter
457                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackerName=%s - CALLED!', $stackerName));
458                 if (empty($stackerName)) {
459                         // No empty stack name
460                         throw new InvalidArgumentException('Parameter "stackerName" is empty');
461                 }
462
463                 // @TODO Please implement this, returning false
464                 /* NOISY-DEBUG: */ $this->partialStub('[' . __METHOD__ . ':' . __LINE__ . '] stackerName=' . $stackerName);
465                 $isFull = false;
466
467                 // Return result
468                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: isFull=%d - EXIT!', intval($isFull)));
469                 return $isFull;
470         }
471
472         /**
473          * Checks whether the given stack is empty
474          *
475          * @param       $stackerName            Name of the stack
476          * @return      $isEmpty                        Whether the stack is empty
477          * @throws      InvalidArgumentException        If a parameter is not valid
478          * @throws      BadMethodCallException  If given stack is missing
479          */
480         public function isStackEmpty (string $stackerName) {
481                 // Validate parameter
482                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackerName=%s - CALLED!', $stackerName));
483                 if (empty($stackerName)) {
484                         // No empty stack name
485                         throw new InvalidArgumentException('Parameter "stackerName" is empty');
486                 }
487
488                 // So, is the stack empty?
489                 $isEmpty = (($this->getStackCount($stackerName)) == 0);
490
491                 // Return result
492                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: isEmpty=%d - EXIT!', intval($isEmpty)));
493                 return $isEmpty;
494         }
495
496         /**
497          * Checks whether the given stack is initialized (set in array $stackers)
498          *
499          * @param       $stackerName    Name of the stack
500          * @return      $isInitialized  Whether the stack is initialized
501          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
502          */
503         public function isStackInitialized (string $stackerName) {
504                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getBinaryFileInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
505         }
506
507         /**
508          * Determines whether the EOF has been reached
509          *
510          * @return      $isEndOfFileReached             Whether the EOF has been reached
511          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
512          */
513         public function isEndOfFileReached () {
514                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getBinaryFileInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
515         }
516
517         /**
518          * Getter for size of given stack (array count)
519          *
520          * @param       $stackerName    Name of the stack
521          * @return      $count                  Size of stack (array count)
522          */
523         public function getStackCount (string $stackerName) {
524                 // Now, simply return the found count value, this must be up-to-date then!
525                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackerName=%s - CALLED!', $stackerName));
526                 $count = $this->getIteratorInstance()->getBinaryFileInstance()->getCounter();
527
528                 // Return count
529                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: count=%d - EXIT!', $count));
530                 return $count;
531         }
532
533         /**
534          * Calculates minimum length for one entry/block
535          *
536          * @return      $length         Minimum length for one entry/block
537          */
538         public function calculateMinimumBlockLength () {
539                 // Is the value "cached"?
540                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: CALLED!');
541                 if (self::$minimumBlockLength == 0) {
542                         // Calulcate it
543                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: Calculating ...');
544                         self::$minimumBlockLength =
545                                 // Length of entry group
546                                 BinaryFile::LENGTH_GROUP + strlen(chr(BinaryFile::SEPARATOR_GROUP_HASH)) +
547                                 // Hash + value
548                                 self::getHashLength() + strlen(chr(BinaryFile::SEPARATOR_HASH_VALUE)) + 1 +
549                                 // Final separator
550                                 strlen(chr(BinaryFile::SEPARATOR_ENTRIES));
551                 }
552
553                 // Return it
554                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: self::minimumBlockLength=%d - EXIT!', self::$minimumBlockLength));
555                 return self::$minimumBlockLength;
556         }
557
558         /**
559          * Initializes counter for valid entries, arrays for damaged entries and
560          * an array for gap seek positions. If you call this method on your own,
561          * please re-analyze the file structure. So you are better to call
562          * analyzeFileStructure() instead of this method.
563          *
564          * @return      void
565          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
566          */
567         public function initCountersGapsArray () {
568                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getBinaryFileInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
569         }
570
571         /**
572          * Getter for header size
573          *
574          * @return      $totalEntries   Size of file header
575          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
576          */
577         public final function getHeaderSize () {
578                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getBinaryFileInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
579         }
580
581         /**
582          * Setter for header size
583          *
584          * @param       $headerSize             Size of file header
585          * @return      void
586          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
587          */
588         public final function setHeaderSize (int $headerSize) {
589                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getBinaryFileInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
590         }
591
592         /**
593          * Getter for header array
594          *
595          * @return      $totalEntries   Size of file header
596          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
597          */
598         public final function getHeader () {
599                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getBinaryFileInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
600         }
601
602         /**
603          * Setter for header
604          *
605          * @param       $header         Array for a file header
606          * @return      void
607          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
608          */
609         public final function setHeader (array $header) {
610                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getBinaryFileInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
611         }
612
613         /**
614          * Updates seekPosition attribute from file to avoid to much access on file.
615          *
616          * @return      void
617          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
618          */
619         public function updateSeekPosition () {
620                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getBinaryFileInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
621         }
622
623         /**
624          * Getter for total entries
625          *
626          * @return      $totalEntries   Total entries in this file
627          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
628          */
629         public final function getCounter () {
630                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getBinaryFileInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
631         }
632
633         /**
634          * Writes data at given position
635          *
636          * @param       $seekPosition   Seek position
637          * @param       $data                   Data to be written
638          * @param       $flushHeader    Whether to flush the header (default: flush)
639          * @return      void
640          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
641          */
642         public function writeData (int $seekPosition, string $data, bool $flushHeader = true) {
643                 // Not supported
644                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getBinaryFileInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
645         }
646
647         /**
648          * Writes at given position by seeking to it.
649          *
650          * @param       $seekPosition   Seek position in file
651          * @param       $dataStream             Data to be written
652          * @return      mixed                   Number of writes bytes or false on error
653          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
654          */
655         public function writeAtPosition (int $seekPosition, string $dataStream) {
656                 // Not supported
657                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getBinaryFileInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
658         }
659
660         /**
661          * Writes given value to the file and returns a hash and gap position for it
662          *
663          * @param       $stackName      Group identifier
664          * @param       $value          Value to be added to the stack
665          * @return      $data           Hash and gap position
666          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
667          */
668         public function writeValueToFile (string $stackName, $value) {
669                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getBinaryFileInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
670         }
671
672         /**
673          * Searches for next suitable gap the given length of data can fit in
674          * including padding bytes.
675          *
676          * @param       $length                 Length of raw data
677          * @return      $seekPosition   Found next gap's seek position
678          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
679          */
680         public function searchNextGap (int $length) {
681                 // Not supported here
682                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getBinaryFileInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
683         }
684
685         /**
686          * "Getter" for file size
687          *
688          * @return      $fileSize       Size of currently loaded file
689          */
690         public function getFileSize () {
691                 // Call iterator's method
692                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: CALLED!');
693                 $size = $this->getIteratorInstance()->getBinaryFileInstance()->getFileSize();
694
695                 // Return size
696                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: size=%d - EXIT!', $size));
697                 return $size;
698         }
699
700         /**
701          * Writes given raw data to the file and returns a gap position and length
702          *
703          * @param       $stackName      Group identifier
704          * @param       $hash           Hash from encoded value
705          * @param       $encoded        Encoded value to be written to the file
706          * @return      $data           Gap position and length of the raw data
707          */
708         public function writeDataToFreeGap (string $stackName, string $hash, string $encoded) {
709                 // Raw data been written to the file
710                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackName=%s,hash=%s,encoded()=%d - CALLED!', $stackName, $hash, strlen($encoded)));
711                 $rawData = sprintf('%s%s%s%s%s',
712                         $stackName,
713                         BinaryFile::SEPARATOR_GROUP_HASH,
714                         hex2bin($hash),
715                         BinaryFile::SEPARATOR_HASH_VALUE,
716                         $encoded
717                 );
718
719                 // Search for next free gap
720                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackName=%s,hash=%s,rawData()=%d', $stackName, $hash, strlen($rawData)));
721                 $gapPosition = $this->getIteratorInstance()->getBinaryFileInstance()->searchNextGap(strlen($rawData));
722
723                 // Gap position cannot be smaller than header length + 1
724                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: gapPosition=%d', $gapPosition));
725                 if ($gapPosition <= $this->getIteratorInstance()->getBinaryFileInstance()->getHeaderSize()) {
726                         // Improper gap position
727                         throw new UnexpectedValueException(sprintf('gapPosition[%s]=%d is not larger than headerSize=%d',
728                                 gettype($gapPosition),
729                                 $gapPosition,
730                                 $this->getIteratorInstance()->getBinaryFileInstance()->getHeaderSize()
731                         ));
732                 }
733
734                 // Then write the data at that gap
735                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackName=%s,hash=%s,gapPosition=%s', $stackName, $hash, $gapPosition));
736                 $this->getIteratorInstance()->getBinaryFileInstance()->writeData($gapPosition, $rawData);
737
738                 // Return gap position, hash and length of raw data
739                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackName=%s,hash=%s,rawData()=%d - EXIT!', $stackName, $hash, strlen($rawData)));
740                 return [
741                         StackableFile::ARRAY_NAME_GAP_POSITION => $gapPosition,
742                         StackableFile::ARRAY_NAME_HASH         => $hash,
743                         StackableFile::ARRAY_NAME_DATA_LENGTH  => strlen($rawData),
744                 ];
745         }
746
747 }