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