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