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\FrameworkInterface;
11 use Org\Mxchange\CoreFramework\Generic\UnsupportedOperationException;
12 use Org\Mxchange\CoreFramework\Middleware\Debug\DebugMiddleware;
13 use Org\Mxchange\CoreFramework\Stack\BaseStacker;
14 use Org\Mxchange\CoreFramework\Traits\Index\IndexableTrait;
15 use Org\Mxchange\CoreFramework\Traits\Iterator\IteratorTrait;
16 use Org\Mxchange\CoreFramework\Utils\Arrays\ArrayUtils;
17 use Org\Mxchange\CoreFramework\Utils\Strings\StringUtils;
18
19 // Import SPL stuff
20 use \InvalidArgumentException;
21 use \SplFileInfo;
22 use \UnexpectedValueException;
23
24 /**
25  * A general file-based stack class
26  *
27  * @author              Roland Haeder <webmaster@ship-simu.org>
28  * @version             0.0.0
29  * @copyright   Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2023 Core Developer Team
30  * @license             GNU GPL 3.0 or any newer version
31  * @link                http://www.ship-simu.org
32  *
33  * This program is free software: you can redistribute it and/or modify
34  * it under the terms of the GNU General Public License as published by
35  * the Free Software Foundation, either version 3 of the License, or
36  * (at your option) any later version.
37  *
38  * This program is distributed in the hope that it will be useful,
39  * but WITHOUT ANY WARRANTY; without even the implied warranty of
40  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
41  * GNU General Public License for more details.
42  *
43  * You should have received a copy of the GNU General Public License
44  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
45  */
46 abstract class BaseFileStack extends BaseStacker {
47         // Load traits
48         use IndexableTrait;
49         use IteratorTrait;
50
51         // Exception codes
52         const EXCEPTION_BAD_MAGIC = 0xe100;
53
54         /**
55          * Minimum block length
56          */
57         private static $minimumBlockLength = 0;
58
59         /**
60          * Protected constructor
61          *
62          * @param       $className      Name of the class
63          * @return      void
64          */
65         protected function __construct (string $className) {
66                 // Call parent constructor
67                 parent::__construct($className);
68         }
69
70         /**
71          * Reads the file header
72          *
73          * @return      void
74          * @todo        To hard assertions here, better rewrite them to exceptions
75          * @throws      UnexpectedValueException        If header is not proper length
76          * @throws      InvalidMagicException   If a bad magic was found
77          */
78         public function readStackHeader () {
79                 // First rewind to beginning as the header sits at the beginning ...
80                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: CALLED!');
81                 $this->getIteratorInstance()->rewind();
82
83                 // Get header size
84                 $headerSize = $this->getIteratorInstance()->getBinaryFileInstance()->getHeaderSize();
85
86                 // Then read it (see constructor for calculation)
87                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: headerSize=%d', $headerSize));
88                 $data = $this->getIteratorInstance()->getBinaryFileInstance()->read($headerSize);
89
90                 // Have all requested bytes been read?
91                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: Read %d bytes (%d wanted).', strlen($data), $this->getIteratorInstance()->getBinaryFileInstance()->getHeaderSize()));
92                 if (strlen($data) != $this->getIteratorInstance()->getBinaryFileInstance()->getHeaderSize()) {
93                         // Bad data length
94                         throw new UnexpectedValueException(sprintf('data(%d)=%s does not match iteratorInstance->headerSize=%d',
95                                 strlen($data),
96                                 $data,
97                                 $this->getIteratorInstance()->getBinaryFileInstance()->getHeaderSize()
98                         ), FrameworkInterface::EXCEPTION_UNEXPECTED_VALUE);
99                 } elseif (empty(trim($data, chr(0)))) {
100                         // Empty header, file is freshly pre-allocated
101                         /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: Empty file header detected - EXIT!');
102                         return;
103                 }
104
105                 // Last character must be the separator
106                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: data(-1)=0x%s', dechex(ord(substr($data, -1, 1)))));
107                 if (substr($data, -1, 1) !== chr(BinaryFile::SEPARATOR_HEADER_ENTRIES)) {
108                         // Not valid separator
109                         throw new UnexpectedValueException(sprintf('data=%s does not have separator=0x%s at the end.',
110                                 $data,
111                                 dechex(BinaryFile::SEPARATOR_HEADER_ENTRIES)
112                         ), FrameworkInterface::EXCEPTION_UNEXPECTED_VALUE);
113                 }
114
115                 // Okay, then remove it
116                 $data = substr($data, 0, -1);
117
118                 // And update seek position
119                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: Invoking this->iteratorInstance->binaryFileInstance->updateSeekPosition() ...');
120                 $this->getIteratorInstance()->getBinaryFileInstance()->updateSeekPosition();
121
122                 /*
123                  * Now split it:
124                  *
125                  * 0 => magic
126                  * 1 => total entries
127                  * 2 => current seek position
128                  */
129                 $header = explode(chr(BinaryFile::SEPARATOR_HEADER_DATA), $data);
130
131                 // Map numeric indexes to associative indexes
132                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: Invoking ArrayUtils::mapNumericKeysToAssociative(%d) ...', count($header)));
133                 $header = ArrayUtils::mapNumericKeysToAssociative($header, [
134                         BinaryFile::HEADER_NAME_MAGIC,
135                         BinaryFile::HEADER_NAME_TOTAL_ENTRIES,
136                         BinaryFile::HEADER_NAME_SEEK_POSITION,
137                 ]);
138
139                 // Check if the array has only 3 elements
140                 /* 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)));
141                 //* 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)));
142                 if (count($header) != BinaryFile::HEADER_STACK_ELEMENT_COUNT) {
143                         // Header array count is not expected
144                         throw new UnexpectedValueException(sprintf('data=%s has %d elements, but expected is %d',
145                                 $data,
146                                 count($header),
147                                 BinaryFile::HEADER_STACK_ELEMENT_COUNT
148                         ), FrameworkInterface::EXCEPTION_UNEXPECTED_VALUE);
149                 } elseif ($header[BinaryFile::HEADER_NAME_MAGIC] != StackableFile::STACK_MAGIC) {
150                         // Bad magic
151                         throw new InvalidMagicException($data, self::EXCEPTION_BAD_MAGIC);
152                 } elseif (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                         ), FrameworkInterface::EXCEPTION_UNEXPECTED_VALUE);
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                         ), FrameworkInterface::EXCEPTION_UNEXPECTED_VALUE);
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: Invoking 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', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
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: Invoking 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: Invoking 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: Invoking this->readStackHeader() ...');
285                 $this->readStackHeader();
286
287                 // Is the index loaded correctly, e.g. the stack file is just created?
288                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: Invoking 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: Invoking 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: Invoking 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', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
324                 } elseif ($this->isStackFull($stackerName)) {
325                         // Stacker is full
326                         throw new BadMethodCallException(sprintf('stackerName=%s is full but method call.', $stackerName), FrameworkInterface::EXCEPTION_BAD_METHOD_CALL);
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                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: Invoking this->iteratorInstance->binaryFileInstance->writeValueToFile(%s,value[]=%s) ...', $stackerName, gettype($value)));
337                 $data = $this->getIteratorInstance()->getBinaryFileInstance()->writeValueToFile($stackerName, $value);
338
339                 // Add the hash and gap position to the index
340                 //* PRINTR-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: data=%s', print_r($data, true));
341                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: Invoking this->indexInstance->addHashedDataToIndex(%s,data()=%d) ...', $stackerName, count($data)));
342                 $this->getIndexInstance()->addHashedDataToIndex($stackerName, $data);
343
344                 // Trace message
345                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: EXIT!');
346         }
347
348         /**
349          * Get last value from named stacker
350          *
351          * @param       $stackerName    Name of the stack
352          * @return      $value                  Value of last added value
353          * @throws      InvalidArgumentException        If a parameter is not valid
354          * @throws      BadMethodCallException  If the stack is empty
355          */
356         protected function getLastValue (string $stackerName) {
357                 // Validate parameter
358                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackerName=%s - CALLED!', $stackerName));
359                 if (empty($stackerName)) {
360                         // No empty stack name
361                         throw new InvalidArgumentException('Parameter "stackerName" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
362                 } elseif ($this->isStackEmpty($stackerName)) {
363                         // Throw an exception
364                         throw new BadMethodCallException([$this, $stackerName], FrameworkInterface::EXCEPTION_BAD_METHOD_CALL);
365                 }
366
367                 // Now get the last value
368                 /* NOISY-DEBUG: */ DebugMiddleware::getSelfInstance()->partialStub('[' . __METHOD__ . ':' . __LINE__ . '] stackerName=' . $stackerName);
369                 $value = NULL;
370
371                 // Return it
372                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: value[]=%s - EXIT!', gettype($value)));
373                 return $value;
374         }
375
376         /**
377          * Get first value from named stacker
378          *
379          * @param       $stackerName    Name of the stack
380          * @return      $value                  Value of last added value
381          * @throws      InvalidArgumentException        If a parameter is not valid
382          * @throws      BadMethodCallException  If the stack is empty
383          */
384         protected function getFirstValue (string $stackerName) {
385                 // Validate parameter
386                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackerName=%s - CALLED!', $stackerName));
387                 if (empty($stackerName)) {
388                         // No empty stack name
389                         throw new InvalidArgumentException('Parameter "stackerName" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
390                 } elseif ($this->isStackEmpty($stackerName)) {
391                         // Throw an exception
392                         throw new BadMethodCallException([$this, $stackerName], FrameworkInterface::EXCEPTION_BAD_METHOD_CALL);
393                 }
394
395                 // Now get the first value
396                 /* NOISY-DEBUG: */ DebugMiddleware::getSelfInstance()->partialStub('[' . __METHOD__ . ':' . __LINE__ . '] stackerName=' . $stackerName);
397                 $value = NULL;
398
399                 // Return it
400                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: value[]=%s - EXIT!', gettype($value)));
401                 return $value;
402         }
403
404         /**
405          * "Pops" last entry from stack
406          *
407          * @param       $stackerName    Name of the stack
408          * @return      $value                  Value "poped" from array
409          * @throws      InvalidArgumentException        If a parameter is not valid
410          * @throws      BadMethodCallException  If the stack is empty
411          */
412         protected function popLast (string $stackerName) {
413                 // Validate parameter
414                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackerName=%s - CALLED!', $stackerName));
415                 if (empty($stackerName)) {
416                         // No empty stack name
417                         throw new InvalidArgumentException('Parameter "stackerName" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
418                 } elseif ($this->isStackEmpty($stackerName)) {
419                         // Throw an exception
420                         throw new BadMethodCallException([$this, $stackerName], FrameworkInterface::EXCEPTION_BAD_METHOD_CALL);
421                 }
422
423                 // Now, remove the last entry, we don't care about the return value here, see elseif() block above
424                 /* NOISY-DEBUG: */ DebugMiddleware::getSelfInstance()->partialStub('[' . __METHOD__ . ':' . __LINE__ . '] stackerName=' . $stackerName);
425                 return NULL;
426         }
427
428         /**
429          * "Pops" first entry from stack
430          *
431          * @param       $stackerName    Name of the stack
432          * @return      $value                  Value "shifted" from array
433          * @throws      InvalidArgumentException        If a parameter is not valid
434          * @throws      BadMethodCallException  If the named stacker is empty
435          */
436         protected function popFirst (string $stackerName) {
437                 // Validate parameter
438                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackerName=%s - CALLED!', $stackerName));
439                 if (empty($stackerName)) {
440                         // No empty stack name
441                         throw new InvalidArgumentException('Parameter "stackerName" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
442                 } elseif ($this->isStackEmpty($stackerName)) {
443                         // Throw an exception
444                         throw new BadMethodCallException([$this, $stackerName], FrameworkInterface::EXCEPTION_BAD_METHOD_CALL);
445                 }
446
447                 // Now, remove the last entry, we don't care about the return value here, see elseif() block above
448                 /* NOISY-DEBUG: */ DebugMiddleware::getSelfInstance()->partialStub('[' . __METHOD__ . ':' . __LINE__ . '] stackerName=' . $stackerName);
449                 return NULL;
450         }
451
452         /**
453          * Checks whether the given stack is full
454          *
455          * @param       $stackerName    Name of the stack
456          * @return      $isFull                 Whether the stack is full
457          * @throws      InvalidArgumentException        If a parameter is not valid
458          */
459         protected function isStackFull (string $stackerName) {
460                 // Validate parameter
461                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackerName=%s - CALLED!', $stackerName));
462                 if (empty($stackerName)) {
463                         // No empty stack name
464                         throw new InvalidArgumentException('Parameter "stackerName" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
465                 }
466
467                 // @TODO Please implement this, returning false
468                 /* NOISY-DEBUG: */ DebugMiddleware::getSelfInstance()->partialStub('[' . __METHOD__ . ':' . __LINE__ . '] stackerName=' . $stackerName);
469                 $isFull = false;
470
471                 // Return result
472                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: isFull=%d - EXIT!', intval($isFull)));
473                 return $isFull;
474         }
475
476         /**
477          * Checks whether the given stack is empty
478          *
479          * @param       $stackerName            Name of the stack
480          * @return      $isEmpty                        Whether the stack is empty
481          * @throws      InvalidArgumentException        If a parameter is not valid
482          * @throws      BadMethodCallException  If given stack is missing
483          */
484         public function isStackEmpty (string $stackerName) {
485                 // Validate parameter
486                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackerName=%s - CALLED!', $stackerName));
487                 if (empty($stackerName)) {
488                         // No empty stack name
489                         throw new InvalidArgumentException('Parameter "stackerName" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
490                 }
491
492                 // So, is the stack empty?
493                 $isEmpty = (($this->getStackCount($stackerName)) == 0);
494
495                 // Return result
496                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: isEmpty=%d - EXIT!', intval($isEmpty)));
497                 return $isEmpty;
498         }
499
500         /**
501          * Checks whether the given stack is initialized (set in array $stackers)
502          *
503          * @param       $stackerName    Name of the stack
504          * @return      $isInitialized  Whether the stack is initialized
505          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
506          */
507         public function isStackInitialized (string $stackerName) {
508                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getBinaryFileInstance()), FrameworkInterface::EXCEPTION_UNSPPORTED_OPERATION);
509         }
510
511         /**
512          * Determines whether the EOF has been reached
513          *
514          * @return      $isEndOfFileReached             Whether the EOF has been reached
515          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
516          */
517         public function isEndOfFileReached () {
518                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getBinaryFileInstance()), FrameworkInterface::EXCEPTION_UNSPPORTED_OPERATION);
519         }
520
521         /**
522          * Getter for size of given stack (array count)
523          *
524          * @param       $stackerName    Name of the stack
525          * @return      $count                  Size of stack (array count)
526          */
527         public function getStackCount (string $stackerName) {
528                 // Now, simply return the found count value, this must be up-to-date then!
529                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackerName=%s - CALLED!', $stackerName));
530                 $count = $this->getIteratorInstance()->getBinaryFileInstance()->getCounter();
531
532                 // Return count
533                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: count=%d - EXIT!', $count));
534                 return $count;
535         }
536
537         /**
538          * Calculates minimum length for one entry/block
539          *
540          * @return      $length         Minimum length for one entry/block
541          */
542         public function calculateMinimumBlockLength () {
543                 // Is the value "cached"?
544                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: CALLED!');
545                 if (self::$minimumBlockLength == 0) {
546                         // Calulcate it
547                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: Calculating ...');
548                         self::$minimumBlockLength =
549                                 // Length of entry group
550                                 BinaryFile::LENGTH_GROUP + strlen(chr(BinaryFile::SEPARATOR_GROUP_HASH)) +
551                                 // Hash + value
552                                 self::getHashLength() + strlen(chr(BinaryFile::SEPARATOR_HASH_VALUE)) + 1 +
553                                 // Final separator
554                                 strlen(chr(BinaryFile::SEPARATOR_ENTRIES));
555                 }
556
557                 // Return it
558                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: self::minimumBlockLength=%d - EXIT!', self::$minimumBlockLength));
559                 return self::$minimumBlockLength;
560         }
561
562         /**
563          * Initializes counter for valid entries, arrays for damaged entries and
564          * an array for gap seek positions. If you call this method on your own,
565          * please re-analyze the file structure. So you are better to call
566          * analyzeFileStructure() instead of this method.
567          *
568          * @return      void
569          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
570          */
571         public function initCountersGapsArray () {
572                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getBinaryFileInstance()), FrameworkInterface::EXCEPTION_UNSPPORTED_OPERATION);
573         }
574
575         /**
576          * Getter for header size
577          *
578          * @return      $totalEntries   Size of file header
579          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
580          */
581         public final function getHeaderSize () {
582                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getBinaryFileInstance()), FrameworkInterface::EXCEPTION_UNSPPORTED_OPERATION);
583         }
584
585         /**
586          * Setter for header size
587          *
588          * @param       $headerSize             Size of file header
589          * @return      void
590          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
591          */
592         public final function setHeaderSize (int $headerSize) {
593                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getBinaryFileInstance()), FrameworkInterface::EXCEPTION_UNSPPORTED_OPERATION);
594         }
595
596         /**
597          * Getter for header array
598          *
599          * @return      $totalEntries   Size of file header
600          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
601          */
602         public final function getHeader () {
603                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getBinaryFileInstance()), FrameworkInterface::EXCEPTION_UNSPPORTED_OPERATION);
604         }
605
606         /**
607          * Setter for header
608          *
609          * @param       $header         Array for a file header
610          * @return      void
611          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
612          */
613         public final function setHeader (array $header) {
614                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getBinaryFileInstance()), FrameworkInterface::EXCEPTION_UNSPPORTED_OPERATION);
615         }
616
617         /**
618          * Updates seekPosition attribute from file to avoid to much access on file.
619          *
620          * @return      void
621          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
622          */
623         public function updateSeekPosition () {
624                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getBinaryFileInstance()), FrameworkInterface::EXCEPTION_UNSPPORTED_OPERATION);
625         }
626
627         /**
628          * Getter for total entries
629          *
630          * @return      $totalEntries   Total entries in this file
631          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
632          */
633         public final function getCounter () {
634                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getBinaryFileInstance()), FrameworkInterface::EXCEPTION_UNSPPORTED_OPERATION);
635         }
636
637         /**
638          * Writes data at given position
639          *
640          * @param       $seekPosition   Seek position
641          * @param       $data                   Data to be written
642          * @param       $flushHeader    Whether to flush the header (default: flush)
643          * @return      void
644          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
645          */
646         public function writeData (int $seekPosition, string $data, bool $flushHeader = true) {
647                 // Not supported
648                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getBinaryFileInstance()), FrameworkInterface::EXCEPTION_UNSPPORTED_OPERATION);
649         }
650
651         /**
652          * Writes at given position by seeking to it.
653          *
654          * @param       $seekPosition   Seek position in file
655          * @param       $dataStream             Data to be written
656          * @return      mixed                   Number of writes bytes or false on error
657          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
658          */
659         public function writeAtPosition (int $seekPosition, string $dataStream) {
660                 // Not supported
661                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getBinaryFileInstance()), FrameworkInterface::EXCEPTION_UNSPPORTED_OPERATION);
662         }
663
664         /**
665          * Writes given value to the file and returns a hash and gap position for it
666          *
667          * @param       $stackName      Group identifier
668          * @param       $value          Value to be added to the stack
669          * @return      $data           Hash and gap position
670          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
671          */
672         public function writeValueToFile (string $stackName, $value) {
673                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getBinaryFileInstance()), FrameworkInterface::EXCEPTION_UNSPPORTED_OPERATION);
674         }
675
676         /**
677          * Searches for next suitable gap the given length of data can fit in
678          * including padding bytes.
679          *
680          * @param       $length                 Length of raw data
681          * @return      $seekPosition   Found next gap's seek position
682          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
683          */
684         public function searchNextGap (int $length) {
685                 // Not supported here
686                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getBinaryFileInstance()), FrameworkInterface::EXCEPTION_UNSPPORTED_OPERATION);
687         }
688
689         /**
690          * "Getter" for file size
691          *
692          * @return      $fileSize       Size of currently loaded file
693          */
694         public function getFileSize () {
695                 // Call iterator's method
696                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: CALLED!');
697                 $size = $this->getIteratorInstance()->getBinaryFileInstance()->getFileSize();
698
699                 // Return size
700                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: size=%d - EXIT!', $size));
701                 return $size;
702         }
703
704         /**
705          * Writes given raw data to the file and returns a gap position and length
706          *
707          * @param       $stackName      Group identifier
708          * @param       $hash           Hash from encoded value
709          * @param       $encoded        Encoded value to be written to the file
710          * @return      $data           Gap position and length of the raw data
711          * @throws      InvalidArgumentException        If a parameter has an invalid value
712          */
713         public function writeDataToFreeGap (string $stackName, string $hash, string $encoded) {
714                 // Check parameter
715                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackName=%s,hash{}=0x%s,encoded()=%d - CALLED!', $stackName, bin2hex($hash), strlen($encoded)));
716                 if (empty($stackName)) {
717                         // Throw IAE
718                         throw new InvalidArgumentException('Parameter "stackName" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
719                 } elseif (empty($hash)) {
720                         // Throw IAE
721                         throw new InvalidArgumentException('Parameter "hash" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
722                 } elseif (empty($encoded)) {
723                         // Throw IAE
724                         throw new InvalidArgumentException('Parameter "encoded" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
725                 }
726
727                 // Raw data been written to the file
728                 $rawData = sprintf('%s%s%s%s%s',
729                         $stackName,
730                         BinaryFile::SEPARATOR_GROUP_HASH,
731                         $hash,
732                         BinaryFile::SEPARATOR_HASH_VALUE,
733                         $encoded
734                 );
735
736                 // Search for next free gap
737                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackName=%s,hash{}=0x%s,rawData()=%d', $stackName, bin2hex($hash), strlen($rawData)));
738                 $gapPosition = $this->getIteratorInstance()->getBinaryFileInstance()->searchNextGap(strlen($rawData));
739
740                 // Gap position cannot be smaller than header length + 1
741                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: gapPosition=%d', $gapPosition));
742                 if ($gapPosition <= $this->getIteratorInstance()->getBinaryFileInstance()->getHeaderSize()) {
743                         // Improper gap position
744                         throw new UnexpectedValueException(sprintf('gapPosition[%s]=%d is not larger than headerSize=%d',
745                                 gettype($gapPosition),
746                                 $gapPosition,
747                                 $this->getIteratorInstance()->getBinaryFileInstance()->getHeaderSize()
748                         ), FrameworkInterface::EXCEPTION_UNEXPECTED_VALUE);
749                 }
750
751                 // Then write the data at that gap
752                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackName=%s,hash{}=0x%s,gapPosition=%s', $stackName, bin2hex($hash), $gapPosition));
753                 $this->getIteratorInstance()->getBinaryFileInstance()->writeData($gapPosition, $rawData);
754
755                 // Return gap position, hash and length of raw data
756                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackName=%s,hash{}=0x%s,rawData()=%d - EXIT!', $stackName, bin2hex($hash), strlen($rawData)));
757                 return [
758                         StackableFile::ARRAY_NAME_GAP_POSITION => $gapPosition,
759                         StackableFile::ARRAY_NAME_HASH         => $hash,
760                         StackableFile::ARRAY_NAME_DATA_LENGTH  => strlen($rawData),
761                 ];
762         }
763
764 }