]> git.mxchange.org Git - core.git/blob - framework/main/classes/stacker/file/class_BaseFileStack.php
3697394752ab2b13166d6e74424d850d2ced1946
[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\FrameworkInterface;
11 use Org\Mxchange\CoreFramework\Generic\UnsupportedOperationException;
12 use Org\Mxchange\CoreFramework\Stack\BaseStacker;
13 use Org\Mxchange\CoreFramework\Traits\Index\IndexableTrait;
14 use Org\Mxchange\CoreFramework\Traits\Iterator\IteratorTrait;
15 use Org\Mxchange\CoreFramework\Utils\Arrays\ArrayUtils;
16 use Org\Mxchange\CoreFramework\Utils\Strings\StringUtils;
17
18 // Import SPL stuff
19 use \InvalidArgumentException;
20 use \SplFileInfo;
21 use \UnexpectedValueException;
22
23 /**
24  * A general file-based stack class
25  *
26  * @author              Roland Haeder <webmaster@ship-simu.org>
27  * @version             0.0.0
28  * @copyright   Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2023 Core Developer Team
29  * @license             GNU GPL 3.0 or any newer version
30  * @link                http://www.ship-simu.org
31  *
32  * This program is free software: you can redistribute it and/or modify
33  * it under the terms of the GNU General Public License as published by
34  * the Free Software Foundation, either version 3 of the License, or
35  * (at your option) any later version.
36  *
37  * This program is distributed in the hope that it will be useful,
38  * but WITHOUT ANY WARRANTY; without even the implied warranty of
39  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
40  * GNU General Public License for more details.
41  *
42  * You should have received a copy of the GNU General Public License
43  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
44  */
45 abstract class BaseFileStack extends BaseStacker {
46         // Load traits
47         use IndexableTrait;
48         use IteratorTrait;
49
50         // Exception codes
51         const EXCEPTION_BAD_MAGIC = 0xe100;
52
53         /**
54          * Minimum block length
55          */
56         private static $minimumBlockLength = 0;
57
58         /**
59          * Protected constructor
60          *
61          * @param       $className      Name of the class
62          * @return      void
63          */
64         protected function __construct (string $className) {
65                 // Call parent constructor
66                 parent::__construct($className);
67         }
68
69         /**
70          * Reads the file header
71          *
72          * @return      void
73          * @todo        To hard assertions here, better rewrite them to exceptions
74          * @throws      UnexpectedValueException        If header is not proper length
75          * @throws      InvalidMagicException   If a bad magic was found
76          */
77         public function readStackHeader () {
78                 // First rewind to beginning as the header sits at the beginning ...
79                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: CALLED!');
80                 $this->getIteratorInstance()->rewind();
81
82                 // Get header size
83                 $headerSize = $this->getIteratorInstance()->getBinaryFileInstance()->getHeaderSize();
84
85                 // Then read it (see constructor for calculation)
86                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: headerSize=%d', $headerSize));
87                 $data = $this->getIteratorInstance()->getBinaryFileInstance()->read($headerSize);
88
89                 // Have all requested bytes been read?
90                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: Read %d bytes (%d wanted).', strlen($data), $this->getIteratorInstance()->getBinaryFileInstance()->getHeaderSize()));
91                 if (strlen($data) != $this->getIteratorInstance()->getBinaryFileInstance()->getHeaderSize()) {
92                         // Bad data length
93                         throw new UnexpectedValueException(sprintf('data(%d)=%s does not match iteratorInstance->headerSize=%d',
94                                 strlen($data),
95                                 $data,
96                                 $this->getIteratorInstance()->getBinaryFileInstance()->getHeaderSize()
97                         ));
98                 } elseif (empty(trim($data, chr(0)))) {
99                         // Empty header, file is freshly pre-allocated
100                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: Empty file header detected - EXIT!');
101                         return;
102                 }
103
104                 // Last character must be the separator
105                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: data(-1)=0x%s', dechex(ord(substr($data, -1, 1)))));
106                 if (substr($data, -1, 1) !== chr(BinaryFile::SEPARATOR_HEADER_ENTRIES)) {
107                         // Not valid separator
108                         throw new UnexpectedValueException(sprintf('data=%s does not have separator=0x%s at the end.',
109                                 $data,
110                                 dechex(BinaryFile::SEPARATOR_HEADER_ENTRIES)
111                         ));
112                 }
113
114                 // Okay, then remove it
115                 $data = substr($data, 0, -1);
116
117                 // And update seek position
118                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: Invoking this->iteratorInstance->binaryFileInstance->updateSeekPosition() ...');
119                 $this->getIteratorInstance()->getBinaryFileInstance()->updateSeekPosition();
120
121                 /*
122                  * Now split it:
123                  *
124                  * 0 => magic
125                  * 1 => total entries
126                  * 2 => current seek position
127                  */
128                 $header = explode(chr(BinaryFile::SEPARATOR_HEADER_DATA), $data);
129
130                 // Map numeric indexes to associative indexes
131                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: Invoking ArrayUtils::mapNumericKeysToAssociative(%d) ...', count($header)));
132                 $header = ArrayUtils::mapNumericKeysToAssociative($header, [
133                         BinaryFile::HEADER_NAME_MAGIC,
134                         BinaryFile::HEADER_NAME_TOTAL_ENTRIES,
135                         BinaryFile::HEADER_NAME_SEEK_POSITION,
136                 ]);
137
138                 // Check if the array has only 3 elements
139                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: HEADER_STACK_ELEMENT_COUNT=%d,header()=%d', BinaryFile::HEADER_STACK_ELEMENT_COUNT, count($header)));
140                 //* PRINTR-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)));
141                 if (count($header) != BinaryFile::HEADER_STACK_ELEMENT_COUNT) {
142                         // Header array count is not expected
143                         throw new UnexpectedValueException(sprintf('data=%s has %d elements, but expected is %d',
144                                 $data,
145                                 count($header),
146                                 BinaryFile::HEADER_STACK_ELEMENT_COUNT
147                         ));
148                 } elseif ($header[BinaryFile::HEADER_NAME_MAGIC] != StackableFile::STACK_MAGIC) {
149                         // Bad magic
150                         throw new InvalidMagicException($data, self::EXCEPTION_BAD_MAGIC);
151                 } elseif (strlen($header[BinaryFile::HEADER_NAME_TOTAL_ENTRIES]) != BinaryFile::LENGTH_COUNT) {
152                         // Count length not valid
153                         throw new UnexpectedValueException(sprintf('header[%s](%d)=%s is not expected %d length',
154                                 BinaryFile::HEADER_NAME_TOTAL_ENTRIES,
155                                 strlen($header[BinaryFile::HEADER_NAME_TOTAL_ENTRIES]),
156                                 $header[BinaryFile::HEADER_NAME_TOTAL_ENTRIES],
157                                 BinaryFile::LENGTH_COUNT
158                         ));
159                 } elseif (strlen($header[BinaryFile::HEADER_NAME_SEEK_POSITION]) != BinaryFile::LENGTH_POSITION) {
160                         // Position length not valid
161                         throw new UnexpectedValueException(sprintf('header[%s](%d)=%s is not expected %d length',
162                                 BinaryFile::HEADER_NAME_SEEK_POSITION,
163                                 strlen($header[BinaryFile::HEADER_NAME_SEEK_POSITION]),
164                                 $header[BinaryFile::HEADER_NAME_SEEK_POSITION],
165                                 BinaryFile::LENGTH_POSITION
166                         ));
167                 }
168
169                 // Decode count and seek position
170                 $header[BinaryFile::HEADER_NAME_TOTAL_ENTRIES] = hex2bin($header[BinaryFile::HEADER_NAME_TOTAL_ENTRIES]);
171                 $header[BinaryFile::HEADER_NAME_SEEK_POSITION] = hex2bin($header[BinaryFile::HEADER_NAME_SEEK_POSITION]);
172
173                 // Set header here
174                 $this->getIteratorInstance()->getBinaryFileInstance()->setHeader($header);
175
176                 // Trace message
177                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: EXIT!');
178         }
179
180         /**
181          * Flushes the file header
182          *
183          * @return      void
184          */
185         public function flushFileHeader () {
186                 // Put all informations together
187                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: CALLED!');
188                 $header = sprintf('%s%s%s%s%s%s',
189                         // Magic
190                         StackableFile::STACK_MAGIC,
191
192                         // Separator magic<->count
193                         chr(BinaryFile::SEPARATOR_HEADER_DATA),
194
195                         // Padded total entries
196                         str_pad(StringUtils::dec2hex($this->getIteratorInstance()->getBinaryFileInstance()->getCounter()), BinaryFile::LENGTH_COUNT, '0', STR_PAD_LEFT),
197
198                         // Separator count<->seek position
199                         chr(BinaryFile::SEPARATOR_HEADER_DATA),
200
201                         // Padded seek position
202                         str_pad(StringUtils::dec2hex($this->getIteratorInstance()->getBinaryFileInstance()->getSeekPosition(), 2), BinaryFile::LENGTH_POSITION, '0', STR_PAD_LEFT),
203
204                         // Separator position<->entries
205                         chr(BinaryFile::SEPARATOR_HEADER_ENTRIES)
206                 );
207
208                 // Write it to disk (header is always at seek position 0)
209                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: Invoking this->iteratorInstance->writeAtPosition(0, header=%s) ...', $header));
210                 $this->getIteratorInstance()->getBinaryFileInstance()->writeAtPosition(0, $header);
211
212                 // Trace message
213                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: EXIT!');
214         }
215
216         /**
217          * Initializes this file-based stack.
218          *
219          * @param       $fileInfoInstance       An instance of a SplFileInfo class
220          * @param       $type           Type of this stack (e.g. url_source for URL sources)
221          * @return      void
222          * @throws      InvalidArgumentException        If a parameter is invalid
223          * @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.
224          */
225         protected function initFileStack (SplFileInfo $fileInfoInstance, string $type) {
226                 // Validate parameter
227                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: fileInfoInstance[%s]=%s,type=%s - CALLED!', get_class($fileInfoInstance), $fileInfoInstance, $type));
228                 if (empty($type)) {
229                         // Invalid parameter
230                         throw new InvalidArgumentException('Parameter "type" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
231                 }
232
233                 // Get a stack file instance
234                 $stackInstance = ObjectFactory::createObjectByConfiguredName('stack_file_class', array($fileInfoInstance, $this));
235
236                 // Get iterator instance
237                 $iteratorInstance = ObjectFactory::createObjectByConfiguredName('file_iterator_class', array($stackInstance));
238
239                 // Set iterator here
240                 $this->setIteratorInstance($iteratorInstance);
241
242                 // Calculate header size
243                 $headerSize = (
244                         strlen(StackableFile::STACK_MAGIC) +
245                         strlen(chr(BinaryFile::SEPARATOR_HEADER_DATA)) +
246                         BinaryFile::LENGTH_COUNT +
247                         strlen(chr(BinaryFile::SEPARATOR_HEADER_DATA)) +
248                         BinaryFile::LENGTH_POSITION +
249                         strlen(chr(BinaryFile::SEPARATOR_HEADER_ENTRIES))
250                 );
251
252                 // Setting it
253                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: Setting headerSize=%d ...', $headerSize));
254                 $this->getIteratorInstance()->getBinaryFileInstance()->setHeaderSize($headerSize);
255
256                 // Init counters and gaps array
257                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: Invoking this->iteratorInstance->initCountersGapsArray() ...');
258                 $this->getIteratorInstance()->getBinaryFileInstance()->initCountersGapsArray();
259
260                 /*
261                  * Get stack index instance. This can be used for faster
262                  * "defragmentation" and startup.
263                  */
264                 /* 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));
265                 $indexInstance = FileStackIndexFactory::createFileStackIndexInstance($fileInfoInstance, $type);
266
267                 // And set it here
268                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: indexInstance=%s', $indexInstance->__toString()));
269                 $this->setIndexInstance($indexInstance);
270
271                 // Is the file's header initialized?
272                 if (!$this->getIteratorInstance()->getBinaryFileInstance()->isFileHeaderInitialized()) {
273                         // First pre-allocate a bit
274                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: Invoking this->iteratorInstance->preAllocateFile(file_stack) ...');
275                         $this->getIteratorInstance()->getBinaryFileInstance()->preAllocateFile('file_stack');
276
277                         // Then create file header
278                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: this->iteratorInstance->createFileHeader() ...');
279                         $this->getIteratorInstance()->getBinaryFileInstance()->createFileHeader();
280                 }
281
282                 // Load the file header
283                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: Invoking this->readStackHeader() ...');
284                 $this->readStackHeader();
285
286                 // Is the index loaded correctly, e.g. the stack file is just created?
287                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: Invoking this->indexInstance->isIndexLoaded() ...');
288                 if (!$this->getIndexInstance()->isIndexLoaded()) {
289                         /*
290                          * Something horrible has happened to the index as it should be
291                          * loaded at this point. The stack's file structure then needs to
292                          * be analyzed and the index rebuild.
293                          */
294                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: Invoking this->iteratorInstance->analyzeFileStructure() ...');
295                         $this->getIteratorInstance()->getBinaryFileInstance()->analyzeFileStructure();
296
297                         // Rebuild index from file
298                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: Invoking this->iteratorInstance->rebuildIndexFromStack(%s) ...', $this->__toString()));
299                         $this->getIndexInstance()->rebuildIndexFromStack($this);
300                 }
301
302                 // Trace message
303                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: EXIT!');
304         }
305
306         /**
307          * Adds a value to given stack
308          *
309          * @param       $stackerName    Name of the stack
310          * @param       $value                  Value to add to this stacker
311          * @return      void
312          * @throws      FullStackerException    If the stack is full
313          * @throws      InvalidArgumentException        If a parameter is not valid
314          * @throws      InvalidArgumentException        Not all variable types are wanted here
315          */
316         protected function addValueToStack (string $stackerName, $value) {
317                 // Validate parameter
318                 /* PRINTR-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackerName=%s,value[]=%s - CALLED!', $stackerName, gettype($value)));
319                 //* PRINTR-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackerName=%s,value[%s]=%s', $stackerName, gettype($value), print_r($value, true)));
320                 if (empty($stackerName)) {
321                         // No empty stack name
322                         throw new InvalidArgumentException('Parameter "stackerName" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
323                 } elseif ($this->isStackFull($stackerName)) {
324                         // Stacker is full
325                         throw new BadMethodCallException(sprintf('stackerName=%s is full but method call.', $stackerName), FrameworkInterface::EXCEPTION_BAD_METHOD_CALL);
326                 } elseif (is_resource($value) || is_object($value)) {
327                         // Not wanted type
328                         throw new InvalidArgumentException(sprintf('value[]=%s is not supported', gettype($value)));
329                 }
330
331                 /*
332                  * Now add the value to the file stack which returns gap position, a
333                  * hash and length of the raw data.
334                  */
335                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: Invoking this->iteratorInstance->binaryFileInstance->writeValueToFile(%s,value[]=%s) ...', $stackerName, gettype($value)));
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: Invoking 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', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
361                 } elseif ($this->isStackEmpty($stackerName)) {
362                         // Throw an exception
363                         throw new BadMethodCallException([$this, $stackerName], FrameworkInterface::EXCEPTION_BAD_METHOD_CALL);
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', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
389                 } elseif ($this->isStackEmpty($stackerName)) {
390                         // Throw an exception
391                         throw new BadMethodCallException([$this, $stackerName], FrameworkInterface::EXCEPTION_BAD_METHOD_CALL);
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', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
417                 } elseif ($this->isStackEmpty($stackerName)) {
418                         // Throw an exception
419                         throw new BadMethodCallException([$this, $stackerName], FrameworkInterface::EXCEPTION_BAD_METHOD_CALL);
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', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
441                 } elseif ($this->isStackEmpty($stackerName)) {
442                         // Throw an exception
443                         throw new BadMethodCallException([$this, $stackerName], FrameworkInterface::EXCEPTION_BAD_METHOD_CALL);
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', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
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', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
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()), FrameworkInterface::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()), FrameworkInterface::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()), FrameworkInterface::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()), FrameworkInterface::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()), FrameworkInterface::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()), FrameworkInterface::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()), FrameworkInterface::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()), FrameworkInterface::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()), FrameworkInterface::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()), FrameworkInterface::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()), FrameworkInterface::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()), FrameworkInterface::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()), FrameworkInterface::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          * @throws      InvalidArgumentException        If a parameter has an invalid value
711          */
712         public function writeDataToFreeGap (string $stackName, string $hash, string $encoded) {
713                 // Check parameter
714                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackName=%s,hash{}=0x%s,encoded()=%d - CALLED!', $stackName, bin2hex($hash), strlen($encoded)));
715                 if (empty($stackName)) {
716                         // Throw IAE
717                         throw new InvalidArgumentException('Parameter "stackName" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
718                 } elseif (empty($hash)) {
719                         // Throw IAE
720                         throw new InvalidArgumentException('Parameter "hash" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
721                 } elseif (empty($encoded)) {
722                         // Throw IAE
723                         throw new InvalidArgumentException('Parameter "encoded" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
724                 }
725
726                 // Raw data been written to the file
727                 $rawData = sprintf('%s%s%s%s%s',
728                         $stackName,
729                         BinaryFile::SEPARATOR_GROUP_HASH,
730                         $hash,
731                         BinaryFile::SEPARATOR_HASH_VALUE,
732                         $encoded
733                 );
734
735                 // Search for next free gap
736                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackName=%s,hash{}=0x%s,rawData()=%d', $stackName, bin2hex($hash), strlen($rawData)));
737                 $gapPosition = $this->getIteratorInstance()->getBinaryFileInstance()->searchNextGap(strlen($rawData));
738
739                 // Gap position cannot be smaller than header length + 1
740                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: gapPosition=%d', $gapPosition));
741                 if ($gapPosition <= $this->getIteratorInstance()->getBinaryFileInstance()->getHeaderSize()) {
742                         // Improper gap position
743                         throw new UnexpectedValueException(sprintf('gapPosition[%s]=%d is not larger than headerSize=%d',
744                                 gettype($gapPosition),
745                                 $gapPosition,
746                                 $this->getIteratorInstance()->getBinaryFileInstance()->getHeaderSize()
747                         ));
748                 }
749
750                 // Then write the data at that gap
751                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackName=%s,hash{}=0x%s,gapPosition=%s', $stackName, bin2hex($hash), $gapPosition));
752                 $this->getIteratorInstance()->getBinaryFileInstance()->writeData($gapPosition, $rawData);
753
754                 // Return gap position, hash and length of raw data
755                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackName=%s,hash{}=0x%s,rawData()=%d - EXIT!', $stackName, bin2hex($hash), strlen($rawData)));
756                 return [
757                         StackableFile::ARRAY_NAME_GAP_POSITION => $gapPosition,
758                         StackableFile::ARRAY_NAME_HASH         => $hash,
759                         StackableFile::ARRAY_NAME_DATA_LENGTH  => strlen($rawData),
760                 ];
761         }
762
763 }