]> git.mxchange.org Git - core.git/blob - framework/main/classes/stacker/file/class_BaseFileStack.php
Continued:
[core.git] / framework / main / classes / stacker / file / class_BaseFileStack.php
1 <?php
2 // Own namespace
3 namespace Org\Mxchange\CoreFramework\Stack\File;
4
5 // Import framework stuff
6 use Org\Mxchange\CoreFramework\Factory\Stack\File\FileStackIndexFactory;
7 use Org\Mxchange\CoreFramework\Factory\Object\ObjectFactory;
8 use Org\Mxchange\CoreFramework\Filesystem\File\BaseBinaryFile;
9 use Org\Mxchange\CoreFramework\Filesystem\File\BinaryFile;
10 use Org\Mxchange\CoreFramework\Generic\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 - 2022 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: Calling 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: Calling 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: Calling 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: Calling 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: Calling 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: Calling 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: Calling 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: Calling 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: Calling 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 FullStackerException([$this, $stackerName, $value], self::EXCEPTION_STACKER_IS_FULL);
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                 $data = $this->getIteratorInstance()->getBinaryFileInstance()->writeValueToFile($stackerName, $value);
336
337                 // Add the hash and gap position to the index
338                 //* PRINTR-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: data=%s', print_r($data, true));
339                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: Calling this->indexInstance->addHashedDataToIndex(%s,data()=%d) ...', $stackerName, count($data)));
340                 $this->getIndexInstance()->addHashedDataToIndex($stackerName, $data);
341
342                 // Trace message
343                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: EXIT!');
344         }
345
346         /**
347          * Get last value from named stacker
348          *
349          * @param       $stackerName    Name of the stack
350          * @return      $value                  Value of last added value
351          * @throws      InvalidArgumentException        If a parameter is not valid
352          * @throws      BadMethodCallException  If the stack is empty
353          */
354         protected function getLastValue (string $stackerName) {
355                 // Validate parameter
356                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackerName=%s - CALLED!', $stackerName));
357                 if (empty($stackerName)) {
358                         // No empty stack name
359                         throw new InvalidArgumentException('Parameter "stackerName" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
360                 } elseif ($this->isStackEmpty($stackerName)) {
361                         // Throw an exception
362                         throw new BadMethodCallException([$this, $stackerName], self::EXCEPTION_STACKER_IS_EMPTY);
363                 }
364
365                 // Now get the last value
366                 /* NOISY-DEBUG: */ $this->partialStub('[' . __METHOD__ . ':' . __LINE__ . '] stackerName=' . $stackerName);
367                 $value = NULL;
368
369                 // Return it
370                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: value[]=%s - EXIT!', gettype($value)));
371                 return $value;
372         }
373
374         /**
375          * Get first value from named stacker
376          *
377          * @param       $stackerName    Name of the stack
378          * @return      $value                  Value of last added value
379          * @throws      InvalidArgumentException        If a parameter is not valid
380          * @throws      BadMethodCallException  If the stack is empty
381          */
382         protected function getFirstValue (string $stackerName) {
383                 // Validate parameter
384                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackerName=%s - CALLED!', $stackerName));
385                 if (empty($stackerName)) {
386                         // No empty stack name
387                         throw new InvalidArgumentException('Parameter "stackerName" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
388                 } elseif ($this->isStackEmpty($stackerName)) {
389                         // Throw an exception
390                         throw new BadMethodCallException([$this, $stackerName], self::EXCEPTION_STACKER_IS_EMPTY);
391                 }
392
393                 // Now get the first value
394                 /* NOISY-DEBUG: */ $this->partialStub('[' . __METHOD__ . ':' . __LINE__ . '] stackerName=' . $stackerName);
395                 $value = NULL;
396
397                 // Return it
398                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: value[]=%s - EXIT!', gettype($value)));
399                 return $value;
400         }
401
402         /**
403          * "Pops" last entry from stack
404          *
405          * @param       $stackerName    Name of the stack
406          * @return      $value                  Value "poped" from array
407          * @throws      InvalidArgumentException        If a parameter is not valid
408          * @throws      BadMethodCallException  If the stack is empty
409          */
410         protected function popLast (string $stackerName) {
411                 // Validate parameter
412                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackerName=%s - CALLED!', $stackerName));
413                 if (empty($stackerName)) {
414                         // No empty stack name
415                         throw new InvalidArgumentException('Parameter "stackerName" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
416                 } elseif ($this->isStackEmpty($stackerName)) {
417                         // Throw an exception
418                         throw new BadMethodCallException([$this, $stackerName], self::EXCEPTION_STACKER_IS_EMPTY);
419                 }
420
421                 // Now, remove the last entry, we don't care about the return value here, see elseif() block above
422                 /* NOISY-DEBUG: */ $this->partialStub('[' . __METHOD__ . ':' . __LINE__ . '] stackerName=' . $stackerName);
423                 return NULL;
424         }
425
426         /**
427          * "Pops" first entry from stack
428          *
429          * @param       $stackerName    Name of the stack
430          * @return      $value                  Value "shifted" from array
431          * @throws      InvalidArgumentException        If a parameter is not valid
432          * @throws      BadMethodCallException  If the named stacker is empty
433          */
434         protected function popFirst (string $stackerName) {
435                 // Validate parameter
436                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackerName=%s - CALLED!', $stackerName));
437                 if (empty($stackerName)) {
438                         // No empty stack name
439                         throw new InvalidArgumentException('Parameter "stackerName" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
440                 } elseif ($this->isStackEmpty($stackerName)) {
441                         // Throw an exception
442                         throw new BadMethodCallException([$this, $stackerName], self::EXCEPTION_STACKER_IS_EMPTY);
443                 }
444
445                 // Now, remove the last entry, we don't care about the return value here, see elseif() block above
446                 /* NOISY-DEBUG: */ $this->partialStub('[' . __METHOD__ . ':' . __LINE__ . '] stackerName=' . $stackerName);
447                 return NULL;
448         }
449
450         /**
451          * Checks whether the given stack is full
452          *
453          * @param       $stackerName    Name of the stack
454          * @return      $isFull                 Whether the stack is full
455          * @throws      InvalidArgumentException        If a parameter is not valid
456          */
457         protected function isStackFull (string $stackerName) {
458                 // Validate parameter
459                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackerName=%s - CALLED!', $stackerName));
460                 if (empty($stackerName)) {
461                         // No empty stack name
462                         throw new InvalidArgumentException('Parameter "stackerName" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
463                 }
464
465                 // @TODO Please implement this, returning false
466                 /* NOISY-DEBUG: */ $this->partialStub('[' . __METHOD__ . ':' . __LINE__ . '] stackerName=' . $stackerName);
467                 $isFull = false;
468
469                 // Return result
470                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: isFull=%d - EXIT!', intval($isFull)));
471                 return $isFull;
472         }
473
474         /**
475          * Checks whether the given stack is empty
476          *
477          * @param       $stackerName            Name of the stack
478          * @return      $isEmpty                        Whether the stack is empty
479          * @throws      InvalidArgumentException        If a parameter is not valid
480          * @throws      BadMethodCallException  If given stack is missing
481          */
482         public function isStackEmpty (string $stackerName) {
483                 // Validate parameter
484                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackerName=%s - CALLED!', $stackerName));
485                 if (empty($stackerName)) {
486                         // No empty stack name
487                         throw new InvalidArgumentException('Parameter "stackerName" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
488                 }
489
490                 // So, is the stack empty?
491                 $isEmpty = (($this->getStackCount($stackerName)) == 0);
492
493                 // Return result
494                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: isEmpty=%d - EXIT!', intval($isEmpty)));
495                 return $isEmpty;
496         }
497
498         /**
499          * Checks whether the given stack is initialized (set in array $stackers)
500          *
501          * @param       $stackerName    Name of the stack
502          * @return      $isInitialized  Whether the stack is initialized
503          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
504          */
505         public function isStackInitialized (string $stackerName) {
506                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getBinaryFileInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
507         }
508
509         /**
510          * Determines whether the EOF has been reached
511          *
512          * @return      $isEndOfFileReached             Whether the EOF has been reached
513          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
514          */
515         public function isEndOfFileReached () {
516                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getBinaryFileInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
517         }
518
519         /**
520          * Getter for size of given stack (array count)
521          *
522          * @param       $stackerName    Name of the stack
523          * @return      $count                  Size of stack (array count)
524          */
525         public function getStackCount (string $stackerName) {
526                 // Now, simply return the found count value, this must be up-to-date then!
527                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackerName=%s - CALLED!', $stackerName));
528                 $count = $this->getIteratorInstance()->getBinaryFileInstance()->getCounter();
529
530                 // Return count
531                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: count=%d - EXIT!', $count));
532                 return $count;
533         }
534
535         /**
536          * Calculates minimum length for one entry/block
537          *
538          * @return      $length         Minimum length for one entry/block
539          */
540         public function calculateMinimumBlockLength () {
541                 // Is the value "cached"?
542                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: CALLED!');
543                 if (self::$minimumBlockLength == 0) {
544                         // Calulcate it
545                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: Calculating ...');
546                         self::$minimumBlockLength =
547                                 // Length of entry group
548                                 BinaryFile::LENGTH_GROUP + strlen(chr(BinaryFile::SEPARATOR_GROUP_HASH)) +
549                                 // Hash + value
550                                 self::getHashLength() + strlen(chr(BinaryFile::SEPARATOR_HASH_VALUE)) + 1 +
551                                 // Final separator
552                                 strlen(chr(BinaryFile::SEPARATOR_ENTRIES));
553                 }
554
555                 // Return it
556                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: self::minimumBlockLength=%d - EXIT!', self::$minimumBlockLength));
557                 return self::$minimumBlockLength;
558         }
559
560         /**
561          * Initializes counter for valid entries, arrays for damaged entries and
562          * an array for gap seek positions. If you call this method on your own,
563          * please re-analyze the file structure. So you are better to call
564          * analyzeFileStructure() instead of this method.
565          *
566          * @return      void
567          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
568          */
569         public function initCountersGapsArray () {
570                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getBinaryFileInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
571         }
572
573         /**
574          * Getter for header size
575          *
576          * @return      $totalEntries   Size of file header
577          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
578          */
579         public final function getHeaderSize () {
580                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getBinaryFileInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
581         }
582
583         /**
584          * Setter for header size
585          *
586          * @param       $headerSize             Size of file header
587          * @return      void
588          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
589          */
590         public final function setHeaderSize (int $headerSize) {
591                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getBinaryFileInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
592         }
593
594         /**
595          * Getter for header array
596          *
597          * @return      $totalEntries   Size of file header
598          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
599          */
600         public final function getHeader () {
601                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getBinaryFileInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
602         }
603
604         /**
605          * Setter for header
606          *
607          * @param       $header         Array for a file header
608          * @return      void
609          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
610          */
611         public final function setHeader (array $header) {
612                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getBinaryFileInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
613         }
614
615         /**
616          * Updates seekPosition attribute from file to avoid to much access on file.
617          *
618          * @return      void
619          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
620          */
621         public function updateSeekPosition () {
622                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getBinaryFileInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
623         }
624
625         /**
626          * Getter for total entries
627          *
628          * @return      $totalEntries   Total entries in this file
629          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
630          */
631         public final function getCounter () {
632                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getBinaryFileInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
633         }
634
635         /**
636          * Writes data at given position
637          *
638          * @param       $seekPosition   Seek position
639          * @param       $data                   Data to be written
640          * @param       $flushHeader    Whether to flush the header (default: flush)
641          * @return      void
642          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
643          */
644         public function writeData (int $seekPosition, string $data, bool $flushHeader = true) {
645                 // Not supported
646                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getBinaryFileInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
647         }
648
649         /**
650          * Writes at given position by seeking to it.
651          *
652          * @param       $seekPosition   Seek position in file
653          * @param       $dataStream             Data to be written
654          * @return      mixed                   Number of writes bytes or false on error
655          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
656          */
657         public function writeAtPosition (int $seekPosition, string $dataStream) {
658                 // Not supported
659                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getBinaryFileInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
660         }
661
662         /**
663          * Writes given value to the file and returns a hash and gap position for it
664          *
665          * @param       $stackName      Group identifier
666          * @param       $value          Value to be added to the stack
667          * @return      $data           Hash and gap position
668          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
669          */
670         public function writeValueToFile (string $stackName, $value) {
671                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getBinaryFileInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
672         }
673
674         /**
675          * Searches for next suitable gap the given length of data can fit in
676          * including padding bytes.
677          *
678          * @param       $length                 Length of raw data
679          * @return      $seekPosition   Found next gap's seek position
680          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
681          */
682         public function searchNextGap (int $length) {
683                 // Not supported here
684                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getBinaryFileInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
685         }
686
687         /**
688          * "Getter" for file size
689          *
690          * @return      $fileSize       Size of currently loaded file
691          */
692         public function getFileSize () {
693                 // Call iterator's method
694                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput('BASE-FILE-STACK: CALLED!');
695                 $size = $this->getIteratorInstance()->getBinaryFileInstance()->getFileSize();
696
697                 // Return size
698                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: size=%d - EXIT!', $size));
699                 return $size;
700         }
701
702         /**
703          * Writes given raw data to the file and returns a gap position and length
704          *
705          * @param       $stackName      Group identifier
706          * @param       $hash           Hash from encoded value
707          * @param       $encoded        Encoded value to be written to the file
708          * @return      $data           Gap position and length of the raw data
709          * @throws      InvalidArgumentException        If a parameter has an invalid value
710          */
711         public function writeDataToFreeGap (string $stackName, string $hash, string $encoded) {
712                 // Check parameter
713                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackName=%s,hash{}=0x%s,encoded()=%d - CALLED!', $stackName, bin2hex($hash), strlen($encoded)));
714                 if (empty($stackName)) {
715                         // Throw IAE
716                         throw new InvalidArgumentException('Parameter "stackName" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
717                 } elseif (empty($hash)) {
718                         // Throw IAE
719                         throw new InvalidArgumentException('Parameter "hash" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
720                 } elseif (empty($encoded)) {
721                         // Throw IAE
722                         throw new InvalidArgumentException('Parameter "encoded" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
723                 }
724
725                 // Raw data been written to the file
726                 $rawData = sprintf('%s%s%s%s%s',
727                         $stackName,
728                         BinaryFile::SEPARATOR_GROUP_HASH,
729                         $hash,
730                         BinaryFile::SEPARATOR_HASH_VALUE,
731                         $encoded
732                 );
733
734                 // Search for next free gap
735                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackName=%s,hash{}=0x%s,rawData()=%d', $stackName, bin2hex($hash), strlen($rawData)));
736                 $gapPosition = $this->getIteratorInstance()->getBinaryFileInstance()->searchNextGap(strlen($rawData));
737
738                 // Gap position cannot be smaller than header length + 1
739                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: gapPosition=%d', $gapPosition));
740                 if ($gapPosition <= $this->getIteratorInstance()->getBinaryFileInstance()->getHeaderSize()) {
741                         // Improper gap position
742                         throw new UnexpectedValueException(sprintf('gapPosition[%s]=%d is not larger than headerSize=%d',
743                                 gettype($gapPosition),
744                                 $gapPosition,
745                                 $this->getIteratorInstance()->getBinaryFileInstance()->getHeaderSize()
746                         ));
747                 }
748
749                 // Then write the data at that gap
750                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackName=%s,hash{}=0x%s,gapPosition=%s', $stackName, bin2hex($hash), $gapPosition));
751                 $this->getIteratorInstance()->getBinaryFileInstance()->writeData($gapPosition, $rawData);
752
753                 // Return gap position, hash and length of raw data
754                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: stackName=%s,hash{}=0x%s,rawData()=%d - EXIT!', $stackName, bin2hex($hash), strlen($rawData)));
755                 return [
756                         StackableFile::ARRAY_NAME_GAP_POSITION => $gapPosition,
757                         StackableFile::ARRAY_NAME_HASH         => $hash,
758                         StackableFile::ARRAY_NAME_DATA_LENGTH  => strlen($rawData),
759                 ];
760         }
761
762 }