]> git.mxchange.org Git - core.git/blob - framework/main/classes/stacker/file/class_BaseFileStack.php
1960259d04732e4c089505674822aca946646acc
[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                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: fileInfoInstance=%s,type=%s - CALLED!', $fileInfoInstance, $type));
240                 $fileInstance = ObjectFactory::createObjectByConfiguredName('stack_file_class', array($fileInfoInstance, $this));
241
242                 // Get iterator instance
243                 $iteratorInstance = ObjectFactory::createObjectByConfiguredName('file_iterator_class', array($fileInstance));
244
245                 // Set iterator here
246                 $this->setIteratorInstance($iteratorInstance);
247
248                 // Calculate header size
249                 $this->getIteratorInstance()->setHeaderSize(
250                         strlen(self::STACK_MAGIC) +
251                         strlen(chr(BaseBinaryFile::SEPARATOR_HEADER_DATA)) +
252                         BaseBinaryFile::LENGTH_COUNT +
253                         strlen(chr(BaseBinaryFile::SEPARATOR_HEADER_DATA)) +
254                         BaseBinaryFile::LENGTH_POSITION +
255                         strlen(chr(BaseBinaryFile::SEPARATOR_HEADER_ENTRIES))
256                 );
257
258                 // Init counters and gaps array
259                 $this->getIteratorInstance()->initCountersGapsArray();
260
261                 // Is the file's header initialized?
262                 if (!$this->getIteratorInstance()->isFileHeaderInitialized()) {
263                         // No, then create it (which may pre-allocate the stack)
264                         $this->getIteratorInstance()->createFileHeader();
265
266                         // And pre-allocate a bit
267                         $this->getIteratorInstance()->preAllocateFile('file_stack');
268                 }
269
270                 // Load the file header
271                 $this->readFileHeader();
272
273                 // Count all entries in file
274                 $this->getIteratorInstance()->analyzeFile();
275
276                 /*
277                  * Get stack index instance. This can be used for faster
278                  * "defragmentation" and startup.
279                  */
280                 $indexInstance = FileStackIndexFactory::createFileStackIndexInstance($fileInfoInstance, $type);
281
282                 // And set it here
283                 $this->setIndexInstance($indexInstance);
284         }
285
286         /**
287          * Adds a value to given stack
288          *
289          * @param       $stackerName    Name of the stack
290          * @param       $value                  Value to add to this stacker
291          * @return      void
292          * @throws      FullStackerException    If the stack is full
293          * @throws      InvalidArgumentException        Not all variable types are wanted here
294          */
295         protected function addValue (string $stackerName, $value) {
296                 // Do some tests
297                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('stackerName=%s,value[%s]=%s - CALLED!', $stackerName, gettype($value), print_r($value, true)));
298                 if ($this->isStackFull($stackerName)) {
299                         // Stacker is full
300                         throw new FullStackerException(array($this, $stackerName, $value), self::EXCEPTION_STACKER_IS_FULL);
301                 } elseif (is_resource($value) || is_object($value)) {
302                         // Not wanted type
303                         throw new InvalidArgumentException(sprintf('value[]=%s is not supported', gettype($value)));
304                 }
305
306                 /*
307                  * Now add the value to the file stack which returns gap position, a
308                  * hash and length of the raw data.
309                  */
310                 $data = $this->getIteratorInstance()->writeValueToFile($stackerName, $value);
311
312                 // Add the hash and gap position to the index
313                 $this->getIndexInstance()->addHashToIndex($stackerName, $data);
314         }
315
316         /**
317          * Get last value from named stacker
318          *
319          * @param       $stackerName    Name of the stack
320          * @return      $value                  Value of last added value
321          * @throws      EmptyStackerException   If the stack is empty
322          */
323         protected function getLastValue (string $stackerName) {
324                 // Is the stack not yet initialized or full?
325                 if ($this->isStackEmpty($stackerName)) {
326                         // Throw an exception
327                         throw new EmptyStackerException(array($this, $stackerName), self::EXCEPTION_STACKER_IS_EMPTY);
328                 } // END - if
329
330                 // Now get the last value
331                 /* NOISY-DEBUG: */ $this->partialStub('[' . __METHOD__ . ':' . __LINE__ . '] stackerName=' . $stackerName);
332                 $value = NULL;
333
334                 // Return it
335                 return $value;
336         }
337
338         /**
339          * Get first value from named stacker
340          *
341          * @param       $stackerName    Name of the stack
342          * @return      $value                  Value of last added value
343          * @throws      EmptyStackerException   If the stack is empty
344          */
345         protected function getFirstValue (string $stackerName) {
346                 // Is the stack not yet initialized or full?
347                 if ($this->isStackEmpty($stackerName)) {
348                         // Throw an exception
349                         throw new EmptyStackerException(array($this, $stackerName), self::EXCEPTION_STACKER_IS_EMPTY);
350                 } // END - if
351
352                 // Now get the first value
353                 /* NOISY-DEBUG: */ $this->partialStub('[' . __METHOD__ . ':' . __LINE__ . '] stackerName=' . $stackerName);
354                 $value = NULL;
355
356                 // Return it
357                 return $value;
358         }
359
360         /**
361          * "Pops" last entry from stack
362          *
363          * @param       $stackerName    Name of the stack
364          * @return      $value                  Value "poped" from array
365          * @throws      EmptyStackerException   If the stack is empty
366          */
367         protected function popLast (string $stackerName) {
368                 // Is the stack not yet initialized or full?
369                 if ($this->isStackEmpty($stackerName)) {
370                         // Throw an exception
371                         throw new EmptyStackerException(array($this, $stackerName), self::EXCEPTION_STACKER_IS_EMPTY);
372                 } // END - if
373
374                 // Now, remove the last entry, we don't care about the return value here, see elseif() block above
375                 /* NOISY-DEBUG: */ $this->partialStub('[' . __METHOD__ . ':' . __LINE__ . '] stackerName=' . $stackerName);
376                 return NULL;
377         }
378
379         /**
380          * "Pops" first entry from stack
381          *
382          * @param       $stackerName    Name of the stack
383          * @return      $value                  Value "shifted" from array
384          * @throws      EmptyStackerException   If the named stacker is empty
385          */
386         protected function popFirst (string $stackerName) {
387                 // Is the stack not yet initialized or full?
388                 if ($this->isStackEmpty($stackerName)) {
389                         // Throw an exception
390                         throw new EmptyStackerException(array($this, $stackerName), self::EXCEPTION_STACKER_IS_EMPTY);
391                 } // END - if
392
393                 // Now, remove the last entry, we don't care about the return value here, see elseif() block above
394                 /* NOISY-DEBUG: */ $this->partialStub('[' . __METHOD__ . ':' . __LINE__ . '] stackerName=' . $stackerName);
395                 return NULL;
396         }
397
398         /**
399          * Checks whether the given stack is full
400          *
401          * @param       $stackerName    Name of the stack
402          * @return      $isFull                 Whether the stack is full
403          */
404         protected function isStackFull (string $stackerName) {
405                 // File-based stacks will only run full if the disk space is low.
406                 // @TODO Please implement this, returning false
407                 $isFull = false;
408
409                 // Return result
410                 return $isFull;
411         }
412
413         /**
414          * Checks whether the given stack is empty
415          *
416          * @param       $stackerName            Name of the stack
417          * @return      $isEmpty                        Whether the stack is empty
418          * @throws      NoStackerException      If given stack is missing
419          */
420         public function isStackEmpty (string $stackerName) {
421                 // So, is the stack empty?
422                 $isEmpty = (($this->getStackCount($stackerName)) == 0);
423
424                 // Return result
425                 return $isEmpty;
426         }
427
428         /**
429          * Initializes given stacker
430          *
431          * @param       $stackerName    Name of the stack
432          * @param       $forceReInit    Force re-initialization
433          * @return      void
434          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
435          */
436         public function initStack (string $stackerName, bool $forceReInit = false) {
437                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getPointerInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
438         }
439
440         /**
441          * Initializes all stacks
442          *
443          * @return      void
444          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
445          */
446         public function initStacks (array $stacks, bool $forceReInit = false) {
447                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getPointerInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
448         }
449
450         /**
451          * Checks whether the given stack is initialized (set in array $stackers)
452          *
453          * @param       $stackerName    Name of the stack
454          * @return      $isInitialized  Whether the stack is initialized
455          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
456          */
457         public function isStackInitialized (string $stackerName) {
458                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getPointerInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
459         }
460
461         /**
462          * Determines whether the EOF has been reached
463          *
464          * @return      $isEndOfFileReached             Whether the EOF has been reached
465          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
466          */
467         public function isEndOfFileReached () {
468                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getPointerInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
469         }
470
471         /**
472          * Getter for size of given stack (array count)
473          *
474          * @param       $stackerName    Name of the stack
475          * @return      $count                  Size of stack (array count)
476          */
477         public function getStackCount (string $stackerName) {
478                 // Now, simply return the found count value, this must be up-to-date then!
479                 return $this->getIteratorInstance()->getCounter();
480         }
481
482         /**
483          * Calculates minimum length for one entry/block
484          *
485          * @return      $length         Minimum length for one entry/block
486          */
487         public function calculateMinimumBlockLength () {
488                 // Calulcate it
489                 $length =
490                         // Length of entry group
491                         BaseBinaryFile::LENGTH_GROUP + strlen(chr(BaseBinaryFile::SEPARATOR_GROUP_HASH)) +
492                         // Hash + value
493                         self::getHashLength() + strlen(chr(BaseBinaryFile::SEPARATOR_HASH_VALUE)) + 1 +
494                         // Final separator
495                         strlen(chr(BaseBinaryFile::SEPARATOR_ENTRIES));
496
497                 // Return it
498                 return $length;
499         }
500
501         /**
502          * Initializes counter for valid entries, arrays for damaged entries and
503          * an array for gap seek positions. If you call this method on your own,
504          * please re-analyze the file structure. So you are better to call
505          * analyzeFile() instead of this method.
506          *
507          * @return      void
508          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
509          */
510         public function initCountersGapsArray () {
511                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getPointerInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
512         }
513
514         /**
515          * Getter for header size
516          *
517          * @return      $totalEntries   Size of file header
518          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
519          */
520         public final function getHeaderSize () {
521                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getPointerInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
522         }
523
524         /**
525          * Setter for header size
526          *
527          * @param       $headerSize             Size of file header
528          * @return      void
529          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
530          */
531         public final function setHeaderSize (int $headerSize) {
532                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getPointerInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
533         }
534
535         /**
536          * Getter for header array
537          *
538          * @return      $totalEntries   Size of file header
539          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
540          */
541         public final function getHeader () {
542                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getPointerInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
543         }
544
545         /**
546          * Setter for header
547          *
548          * @param       $header         Array for a file header
549          * @return      void
550          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
551          */
552         public final function setHeader (array $header) {
553                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getPointerInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
554         }
555
556         /**
557          * Updates seekPosition attribute from file to avoid to much access on file.
558          *
559          * @return      void
560          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
561          */
562         public function updateSeekPosition () {
563                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getPointerInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
564         }
565
566         /**
567          * Getter for total entries
568          *
569          * @return      $totalEntries   Total entries in this file
570          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
571          */
572         public final function getCounter () {
573                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getPointerInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
574         }
575
576         /**
577          * Writes data at given position
578          *
579          * @param       $seekPosition   Seek position
580          * @param       $data                   Data to be written
581          * @param       $flushHeader    Whether to flush the header (default: flush)
582          * @return      void
583          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
584          */
585         public function writeData (int $seekPosition, string $data, bool $flushHeader = true) {
586                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: seekPosition=%s,data[]=%s,flushHeader=%d', $seekPosition, gettype($data), intval($flushHeader)));
587                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getPointerInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
588         }
589
590         /**
591          * Writes given value to the file and returns a hash and gap position for it
592          *
593          * @param       $groupId        Group identifier
594          * @param       $value          Value to be added to the stack
595          * @return      $data           Hash and gap position
596          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
597          */
598         public function writeValueToFile (string $groupId, $value) {
599                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: groupId=%s,value[%s]=%s', $groupId, gettype($value), print_r($value, true)));
600                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getPointerInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
601         }
602
603         /**
604          * Searches for next suitable gap the given length of data can fit in
605          * including padding bytes.
606          *
607          * @param       $length                 Length of raw data
608          * @return      $seekPosition   Found next gap's seek position
609          * @throws      UnsupportedOperationException   This method is not (and maybe never will be) supported
610          */
611         public function searchNextGap (int $length) {
612                 // Not supported here
613                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: length=%d - CALLED!', $length));
614                 throw new UnsupportedOperationException(array($this, __FUNCTION__, $this->getIteratorInstance()->getPointerInstance()), self::EXCEPTION_UNSPPORTED_OPERATION);
615         }
616
617         /**
618          * "Getter" for file size
619          *
620          * @return      $fileSize       Size of currently loaded file
621          */
622         public function getFileSize () {
623                 // Call iterator's method
624                 return $this->getIteratorInstance()->getFileSize();
625         }
626
627         /**
628          * Writes given raw data to the file and returns a gap position and length
629          *
630          * @param       $groupId        Group identifier
631          * @param       $hash           Hash from encoded value
632          * @param       $encoded        Encoded value to be written to the file
633          * @return      $data           Gap position and length of the raw data
634          */
635         public function writeDataToFreeGap (string $groupId, string $hash, string $encoded) {
636                 // Raw data been written to the file
637                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: groupId=%s,hash=%s,encoded()=%d - CALLED!', $groupId, $hash, strlen($encoded)));
638                 $rawData = sprintf('%s%s%s%s%s',
639                         $groupId,
640                         BaseBinaryFile::SEPARATOR_GROUP_HASH,
641                         hex2bin($hash),
642                         BaseBinaryFile::SEPARATOR_HASH_VALUE,
643                         $encoded
644                 );
645
646                 // Search for next free gap
647                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: groupId=%s,hash=%s,rawData()=%d', $groupId, $hash, strlen($rawData)));
648                 $gapPosition = $this->getIteratorInstance()->searchNextGap(strlen($rawData));
649
650                 // Gap position cannot be smaller than header length + 1
651                 if ($gapPosition <= $this->getIteratorInstance()->getHeaderSize()) {
652                         // Improper gap position
653                         throw new UnexpectedValueException(sprintf('gapPosition[%s]=%d is not larger than headerSize=%d',
654                                 gettype($gapPosition),
655                                 $gapPosition,
656                                 $this->getIteratorInstance()->getHeaderSize()
657                         ));
658                 }
659
660                 // Then write the data at that gap
661                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: groupId=%s,hash=%s,gapPosition=%s', $groupId, $hash, $gapPosition));
662                 $this->getIteratorInstance()->writeData($gapPosition, $rawData);
663
664                 // Return gap position, hash and length of raw data
665                 /* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugOutput(sprintf('BASE-FILE-STACK: groupId=%s,hash=%s,rawData()=%d - EXIT!', $groupId, $hash, strlen($rawData)));
666                 return [
667                         self::ARRAY_INDEX_GAP_POSITION => $gapPosition,
668                         self::ARRAY_INDEX_HASH         => $hash,
669                         self::ARRAY_INDEX_DATA_LENGTH  => strlen($rawData),
670                 ];
671         }
672
673 }