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