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