Continued:
[core.git] / framework / main / classes / file_directories / binary / class_BaseBinaryFile.php
1 <?php
2 // Own namespace
3 namespace Org\Mxchange\CoreFramework\Filesystem\File;
4
5 // Import framework stuff
6 use Org\Mxchange\CoreFramework\Bootstrap\FrameworkBootstrap;
7 use Org\Mxchange\CoreFramework\EntryPoint\ApplicationEntryPoint;
8 use Org\Mxchange\CoreFramework\Factory\Object\ObjectFactory;
9 use Org\Mxchange\CoreFramework\Filesystem\File\BaseAbstractFile;
10 use Org\Mxchange\CoreFramework\Filesystem\FilePointer;
11 use Org\Mxchange\CoreFramework\Generic\FrameworkInterface;
12 use Org\Mxchange\CoreFramework\Middleware\Debug\DebugMiddleware;
13 use Org\Mxchange\CoreFramework\Traits\Index\IndexableTrait;
14 use Org\Mxchange\CoreFramework\Traits\Stack\StackableTrait;
15
16 // Import SPL stuff
17 use \BadMethodCallException;
18 use \InvalidArgumentException;
19 use \LogicException;
20 use \OutOfBoundsException;
21 use \SplFileInfo;
22 use \UnexpectedValueException;
23
24 /**
25  * A general binary file class
26  *
27  * @author              Roland Haeder <webmaster@ship-simu.org>
28  * @version             0.0.0
29  * @copyright   Copyright (c) 2007, 2008 Roland Haeder, 2009 - 2023 Core Developer Team
30  * @license             GNU GPL 3.0 or any newer version
31  * @link                http://www.ship-simu.org
32  *
33  * This program is free software: you can redistribute it and/or modify
34  * it under the terms of the GNU General Public License as published by
35  * the Free Software Foundation, either version 3 of the License, or
36  * (at your option) any later version.
37  *
38  * This program is distributed in the hope that it will be useful,
39  * but WITHOUT ANY WARRANTY; without even the implied warranty of
40  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
41  * GNU General Public License for more details.
42  *
43  * You should have received a copy of the GNU General Public License
44  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
45  */
46 abstract class BaseBinaryFile extends BaseAbstractFile implements BinaryFile {
47         // Load traits
48         use StackableTrait;
49         use IndexableTrait;
50
51         /**
52          * Configuration cache
53          */
54         private static $configCache = [];
55
56         /**
57          * Current seek position
58          */
59         private $seekPosition = 0;
60
61         /**
62          * Size of header
63          */
64         private $headerSize = 0;
65
66         /**
67          * File header
68          */
69         private $header = [];
70
71         /**
72          * Seek positions for gaps ("fragmentation")
73          */
74         private $gaps = [];
75
76         /**
77          * Seek positions for damaged entries (e.g. mismatching hash sum, ...)
78          */
79         private $damagedEntries = [];
80
81         /**
82          * Back-buffer
83          */
84         private $backBuffer = '';
85
86         /**
87          * Currently loaded block (will be returned by current())
88          */
89         private $currentBlock = '';
90
91         /**
92          * Protected constructor
93          *
94          * @param       $className      Name of the class
95          * @return      void
96          */
97         protected function __construct (string $className) {
98                 // Call parent constructor
99                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: className=%s - CALLED!', $className));
100                 parent::__construct($className);
101
102                 // Trace message
103                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('FRAMEWORK-DIRECTORY-POINTER: EXIT!');
104         }
105
106         /**
107          * Setter for backBuffer field
108          *
109          * @param       $backBuffer             Characters to "store" in back-buffer
110          * @return      void
111          */
112         private function setBackBuffer (string $backBuffer) {
113                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: Setting backBuffer(%d)=%s - CALLED!', strlen($backBuffer), $backBuffer));
114                 $this->backBuffer = $backBuffer;
115         }
116
117         /**
118          * Getter for backBuffer field
119          *
120          * @return      $backBuffer             Characters "stored" in back-buffer
121          */
122         private function getBackBuffer () {
123                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: Getting this->backBuffer(%d)=%s - CALLED!', strlen($this->backBuffer), $this->backBuffer));
124                 return $this->backBuffer;
125         }
126
127         /**
128          * Setter for current field
129          *
130          * @param       $current        Characters to set a currently loaded block
131          * @return      void
132          */
133         private function setCurrentBlock (string $currentBlock) {
134                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: Setting currentBlock(%d)=%s - CALLED!', strlen($currentBlock), $currentBlock));
135                 $this->currentBlock = $currentBlock;
136         }
137
138         /**
139          * Gets currently read data
140          *
141          * @return      $current        Currently read data
142          */
143         public function getCurrentBlock () {
144                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: Getting this->currentBlock(%d)=%s - CALLED!', strlen($this->currentBlock), $this->currentBlock));
145                 return $this->currentBlock;
146         }
147
148         /**
149          * Getter for header size
150          *
151          * @return      $totalEntries   Size of file header
152          */
153         public final function getHeaderSize () {
154                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: Getting this->headerSize=%d - CALLED!', $this->headerSize));
155                 return $this->headerSize;
156         }
157
158         /**
159          * Setter for header size
160          *
161          * @param       $headerSize             Size of file header
162          * @return      void
163          */
164         public final function setHeaderSize (int $headerSize) {
165                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: Setting headerSize=%d - CALLED!', $headerSize));
166                 $this->headerSize = $headerSize;
167         }
168
169         /**
170          * Getter for header array
171          *
172          * @return      $totalEntries   Size of file header
173          */
174         public final function getHeader () {
175                 // Get it
176                 return $this->header;
177         }
178
179         /**
180          * Setter for header
181          *
182          * @param       $header         Array for a file header
183          * @return      void
184          */
185         public final function setHeader (array $header) {
186                 // Set it
187                 $this->header = $header;
188         }
189
190         /**
191          * Getter for seek position
192          *
193          * @return      $seekPosition   Current seek position (stored here in object)
194          */
195         public final function getSeekPosition () {
196                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: Getting this->seekPosition=%d - CALLED!', $this->seekPosition));
197                 return $this->seekPosition;
198         }
199
200         /**
201          * Setter for seek position
202          *
203          * @param       $seekPosition   Current seek position (stored here in object)
204          * @return      void
205          */
206         protected final function setSeekPosition (int $seekPosition) {
207                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: Setting seekPosition=%d - CALLED!', $seekPosition));
208                 $this->seekPosition = $seekPosition;
209         }
210
211         /**
212          * Checks whether the abstracted file only contains gaps by counting all
213          * gaps' bytes together and compare it to total length.
214          *
215          * @return      $isGapsOnly             Whether the abstracted file only contains gaps
216          * @throws      OutOfBoundsException    If calculated file size is larger than actual
217          */
218         public function isFileGapsOnly () {
219                 // Count every gap
220                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: CALLED!');
221                 $gapsSize = 0;
222                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: this->gaps()=%d', count($this->gaps)));
223                 foreach ($this->gaps as $gap) {
224                         // Calculate size of found gap: end-start including both
225                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: gap[%s]=%d,ga[%s]=%d', BinaryFile::GAPS_INDEX_START, $gap[BinaryFile::GAPS_INDEX_START], BinaryFile::GAPS_INDEX_END, $gap[BinaryFile::GAPS_INDEX_END]));
226                         $gapsSize += ($gap[BinaryFile::GAPS_INDEX_END] - $gap[BinaryFile::GAPS_INDEX_START]);
227
228                         // Debug message
229                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: gapsSize=%d', $gapsSize));
230                 }
231
232                 // Total gap size + header size + 1 must be same as file size
233                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: gapsSize=%d,this->headerSize=%d,this->fileSize=%d', $gapsSize, $this->getHeaderSize(), $this->getFileSize()));
234                 $determinedFileSize = ($gapsSize + $this->getHeaderSize() + 1);
235
236                 // Should not be more!
237                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: determinedFileSize=%d,this->fileSize=%d', $determinedFileSize, $this->getFileSize()));
238                 if ($determinedFileSize > $this->getFileSize()) {
239                         // Should not happen
240                         throw new OutOfBoundsException(sprintf('determinedFileSize=%d is larger than this->fileSize=%d', $determinedFileSize, $this->getFileSize()));
241                 }
242
243                 // Is it same?
244                 $isGapsOnly = ($determinedFileSize == $this->getFileSize());
245
246                 // Return flag
247                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: isGapsOnly=%d - EXIT!', intval($isGapsOnly)));
248                 return $isGapsOnly;
249         }
250
251         /**
252          * Marks whole file as gaps-only (freshly created file
253          *
254          * @param       $type   Type of file
255          * @param       $minimumBlockLength             Minimum block length
256          * @return      void
257          */
258         private function markFileGapsOnly (string $type, int $minimumBlockLength) {
259                 // Is config cache there?
260                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: type=%s,minimumBlockLength=%d - CALLED!', $type, $minimumBlockLength));
261                 if (!isset(self::$configCache[$type . '_pre_allocate_count'])) {
262                         // Then set it
263                         self::$configCache[$type . '_pre_allocate_count'] = FrameworkBootstrap::getConfigurationInstance()->getConfigEntry($type . '_pre_allocate_count');
264                 }
265
266                 // Very simple to do ...
267                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: self:configCache[%s_pre_allocate_count]=%d', $type, self::$configCache[$type . '_pre_allocate_count']));
268                 for ($idx = 0; $idx < self::$configCache[$type . '_pre_allocate_count']; $idx++) {
269                         // Calculate start/end positions
270                         $startPosition = $idx * $minimumBlockLength;
271                         $endPosition = $idx * $minimumBlockLength + $minimumBlockLength;
272
273                         // Mark start and end position as gap
274                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: Invoking this->addGap(%d, %d) ...', $startPosition, $endPosition));
275                         $this->addGap($startPosition, $endPosition);
276                 }
277
278                 // Trace message
279                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: EXIT!');
280         }
281
282         /**
283          * Adds a gap for given start and end position
284          *
285          * @param       $startPosition  Start seek position
286          * @param       $endPosition    End seek position
287          * @return      void
288          */
289         private function addGap(int $startPosition, int $endPosition) {
290                 // Push to gaps array
291                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: startPosition=%d,endPosition=%d - CALLED!', $startPosition, $endPosition));
292                 array_push($this->gaps, [
293                         BinaryFile::GAPS_INDEX_START  => $startPosition,
294                         BinaryFile::GAPS_INDEX_END    => $endPosition,
295                 ]);
296
297                 // Trace message
298                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: EXIT!');
299         }
300
301         /**
302          * Initializes the back-buffer by setting it to an empty string.
303          *
304          * @return      void
305          */
306         private function initBackBuffer () {
307                 // Simply call the setter
308                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: CALLED!');
309                 $this->setBackBuffer('');
310
311                 // Trace message
312                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: EXIT!');
313         }
314
315         /**
316          * Seeks to beginning of file, updates seek position in this object and
317          * flushes the header.
318          *
319          * @param       $flushHeader    Wether the file's header should be flushed (default: false)
320          * @return      void
321          */
322         protected function rewindUpdateSeekPosition (bool $flushHeader = false) {
323                 // Seek to beginning of file
324                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: flushHeader=%d - CALLED!', intval($flushHeader)));
325                 $this->rewind();
326
327                 // And update seek position ...
328                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: Invoking this->updateSeekPosition() ...');
329                 $this->updateSeekPosition();
330
331                 // Flush headers?
332                 if ($flushHeader) {
333                         // ... to write it back into the file
334                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: Invoking this->flushFileHeader() ...');
335                         $this->flushFileHeader();
336                 }
337
338                 // Trace message
339                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: EXIT!');
340         }
341
342         /**
343          * Seeks to old position
344          *
345          * @return      void
346          */
347         protected function seekToOldPosition () {
348                 // Seek to currently ("old") saved position
349                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: CALLED!');
350                 $this->seek($this->determineSeekPosition());
351
352                 // Trace message
353                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: EXIT!');
354         }
355
356         /**
357          * Initializes this file class
358          *
359          * @param       $fileInfoInstance       An instance of a SplFileInfo class
360          * @return      void
361          */
362         protected function initFile (SplFileInfo $fileInfoInstance) {
363                 // Get a file i/o pointer instance
364                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: fileInfoInstance[%s]=%s - CALLED!', get_class($fileInfoInstance), $fileInfoInstance));
365                 $pointerInstance = ObjectFactory::createObjectByConfiguredName('file_raw_input_output_class', array($fileInfoInstance));
366
367                 // ... and set it here
368                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: Setting pointerInstance=%s ...', $pointerInstance->__toString()));
369                 $this->setPointerInstance($pointerInstance);
370
371                 // Trace message
372                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: EXIT!');
373         }
374
375         /**
376          * Marks the currently loaded block as empty (with length of the block)
377          *
378          * @param       $length         Length of the block
379          * @return      void
380          * @throws      InvalidArgumentException        If a parameter is invalid
381          */
382         protected function markCurrentBlockAsEmpty (int $length) {
383                 // Validate parameter
384                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: length=%d - CALLED!', $length));
385                 if ($length < 1) {
386                         // Length cannot below one
387                         throw new InvalidArgumentException(sprintf('length=%d is not valid', $length), FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
388                 }
389
390                 // Get current seek position
391                 $currentPosition = $this->determineSeekPosition();
392
393                 // Now add it as gap entry
394                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: Invoking this->addGap(%d, %d) ..', ($currentPosition - $length), $currentPosition));
395                 $this->addGap(($currentPosition - $length), $currentPosition);
396
397                 // Trace message
398                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: EXIT!');
399         }
400
401         /**
402          * Initializes counter for valid entries, arrays for damaged entries and
403          * an array for gap seek positions. If you call this method on your own,
404          * please re-analyze the file structure. So you are better to call
405          * analyzeFileStructure() instead of this method.
406          *
407          * @return      void
408          */
409         public function initCountersGapsArray () {
410                 // Init counter and seek position to header size
411                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: Invoking this->determineSeekPosition() - CALLED!');
412                 $seekPosition = $this->getSeekPosition();
413
414                 // Set counter
415                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: seekPosition=%d', $seekPosition));
416                 $this->setCounter(0);
417
418                 // Get header size
419                 $headerSize = $this->getHeaderSize();
420
421                 // Set it
422                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: Setting this->seekPosition=%d ...', $headerSize));
423                 $this->setSeekPosition($headerSize);
424
425                 // Init arrays
426                 $this->gaps = [];
427                 $this->damagedEntries = [];
428
429                 // Seek back to old position
430                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: Invoking this->seek(%d) ...', $seekPosition));
431                 $this->seek($seekPosition);
432
433                 // Trace message
434                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: EXIT!');
435         }
436
437         /**
438          * Updates seekPosition attribute from file to avoid to much access on file.
439          *
440          * @return      void
441          */
442         public function updateSeekPosition () {
443                 // Get key (= seek position)
444                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: CALLED!');
445                 $seekPosition = $this->determineSeekPosition();
446
447                 // And set it here
448                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: seekPosition=%d', $seekPosition));
449                 $this->setSeekPosition($seekPosition);
450
451                 // Trace message
452                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: EXIT!');
453         }
454
455         /**
456          * Checks whether the block separator has been found
457          *
458          * @param       $str            String to look in
459          * @return      $isFound        Whether the block separator has been found
460          * @throws      InvalidArgumentException        If a parameter is not valid
461          */
462         public static function isBlockSeparatorFound (string $str) {
463                 // Validate parameter
464                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: str=%s - CALLED!', $str));
465                 if (empty($str)) {
466                         // Throw IAE
467                         throw new InvalidArgumentException('Parameter "str" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
468                 }
469
470                 // Determine it
471                 $isFound = (strpos($str, chr(BinaryFile::SEPARATOR_ENTRIES)) !== false);
472
473                 // Return result
474                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: isFound=%d - EXIT!', intval($isFound)));
475                 return $isFound;
476         }
477
478         /**
479          * Writes data at given position
480          *
481          * @param       $seekPosition   Seek position
482          * @param       $data                   Data to be written
483          * @param       $flushHeader    Whether to flush the header (default: flush)
484          * @return      void
485          * @throws      OutOfBoundsException    If the position is not seekable
486          * @throws      InvalidArgumentException        If a parameter is invalid
487          */
488         public function writeData (int $seekPosition, string $data, bool $flushHeader = true) {
489                 // Validate parameter
490                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: seekPosition=%s,data()=%d,flushHeader=%d - CALLED!', $seekPosition, strlen($data), intval($flushHeader)));
491                 if ($seekPosition < 0) {
492                         // Invalid seek position
493                         throw new OutOfBoundsException(sprintf('seekPosition=%d is not valid', $seekPosition));
494                 } elseif (empty($data)) {
495                         // Empty data is invalid, too
496                         throw new InvalidArgumentException('Parameter "data" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
497                 }
498
499                 // Write data at given position
500                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: Invoking this->writeAtPosition(%d,%s) ...', $seekPosition, $data));
501                 $this->writeAtPosition($seekPosition, $data);
502
503                 // Increment counter
504                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: Invoking this->incrementCounter() ...');
505                 $this->incrementCounter();
506
507                 // Update seek position
508                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: Invoking this->updateSeekPosition() ...');
509                 $this->updateSeekPosition();
510
511                 // Flush the header?
512                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: flushHeader=%d', intval($flushHeader)));
513                 if ($flushHeader === true) {
514                         // Flush header
515                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: Invoking this->flushFileHeader() ...');
516                         $this->flushFileHeader();
517
518                         // Seek to old position
519                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: Invoking this->seekToOldPosition() ...');
520                         $this->seekToOldPosition();
521                 }
522
523                 // Trace message
524                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: EXIT!');
525         }
526
527         /**
528          * Writes at given position by seeking to it.
529          *
530          * @param       $seekPosition   Seek position in file
531          * @param       $dataStream             Data to be written
532          * @return      mixed                   Number of writes bytes or false on error
533          * @throws      OutOfBoundsException    If the position is not seekable
534          * @throws      InvalidArgumentException        If a parameter is not valid
535          */
536         public function writeAtPosition (int $seekPosition, string $dataStream) {
537                 // Validate parameter
538                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: seekPosition=%d,dataStream(%d)=%s - CALLED!', $seekPosition, strlen($dataStream), $dataStream));
539                 if ($seekPosition < 0) {
540                         // Invalid seek position
541                         throw new OutOfBoundsException(sprintf('seekPosition=%d is not valid.', $seekPosition));
542                 } elseif (empty($dataStream)) {
543                         // Empty dataStream
544                         throw new InvalidArgumentException('Parameter "dataStream" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
545                 }
546
547                 // Call pointer's method
548                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: Invoking this->pointerInstance->writeAtPosition(%d, %s) ...', $seekPosition, $dataStream));
549                 $status = $this->getPointerInstance()->writeAtPosition($seekPosition, $dataStream);
550
551                 // Return status
552                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: status[%s]=%d - EXIT!', gettype($status), $status));
553                 return $status;
554         }
555
556         /**
557          * Checks whether the file header is initialized
558          *
559          * @return      $isInitialized  Whether the file header is initialized
560          */
561         public function isFileHeaderInitialized () {
562                 // Default is not initialized
563                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: CALLED!');
564                 $isInitialized = false;
565
566                 // Is the file initialized?
567                 if ($this->isFileInitialized()) {
568                         // Some bytes has been written, so rewind to start of it.
569                         $this->rewind();
570
571                         // Read file header
572                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: Invoking this->readFileHeader() ...');
573                         $this->readFileHeader();
574
575                         // Get header count
576                         $headerCount = count($this->getHeader());
577
578                         // The above method does already check the header
579                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: headerCount=%d', $headerCount));
580                         $isInitialized = ($headerCount > 0);
581                 }
582
583                 // Return result
584                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: isInitialized=%d - EXIT!', intval($isInitialized)));
585                 return $isInitialized;
586         }
587
588         /**
589          * Checks whether the assigned file has been initialized
590          *
591          * @return      $isInitialized          Whether the file's size is zero
592          * @throws      UnexpectedValueException        If an unexpected value was returned
593          */
594         public function isFileInitialized () {
595                 // Get it from iterator which holds the pointer instance. If false is returned
596                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: CALLED!');
597                 $fileSize = $this->size();
598
599                 /*
600                  * The returned file size should not be false or NULL as this means
601                  * that the pointer class does not work correctly.
602                  */
603                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: fileSize[%s]=%d', gettype($fileSize), $fileSize));
604                 if (!is_int($fileSize)) {
605                         // Bad file?
606                         throw new UnexpectedValueException(sprintf('fileSize[]=%s is unexpected', gettype($fileSize)), FrameworkInterface::EXCEPTION_UNEXPECTED_VALUE);
607                 }
608
609                 // Is more than 0 returned?
610                 $isInitialized = ($fileSize > 0);
611
612                 // Return result
613                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: isInitialized=%d - EXIT!', intval($isInitialized)));
614                 return $isInitialized;
615         }
616
617         /**
618          * Creates the assigned file
619          *
620          * @return      void
621          * @throws      BadMethodCallException  If this file's header is already initialized
622          */
623         public function createFileHeader () {
624                 // The file's header should not be initialized here
625                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: CALLED!');
626                 if ($this->isFileHeaderInitialized()) {
627                         // Bad method call
628                         //* DEBUG-DIE: */ ApplicationEntryPoint::exitApplication(sprintf('[%s:%d]: this=%s', __METHOD__, __LINE__, print_r($this, TRUE)));
629                         throw new BadMethodCallException('File header is already initialized but method called', FrameworkInterface::EXCEPTION_BAD_METHOD_CALL);
630                 }
631
632                 // Simple flush file header which will create it.
633                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: Invoking this->flushFileHeader() ...');
634                 $this->flushFileHeader();
635
636                 // Rewind seek position (to beginning of file) and update/flush file header
637                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: Invoking this->rewindUpdateSeekPosition() ...');
638                 $this->rewindUpdateSeekPosition();
639
640                 // Trace message
641                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: EXIT!');
642         }
643
644         /**
645          * Determines seek position
646          *
647          * @return      $seekPosition   Current seek position
648          */
649         public function determineSeekPosition () {
650                 // Call pointer instance
651                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: CALLED!');
652                 $seekPosition = $this->getPointerInstance()->determineSeekPosition();
653
654                 // Return position
655                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: seekPosition=%d - EXIT!', $seekPosition));
656                 return $seekPosition;
657         }
658
659         /**
660          * Seek to given offset (default) or other possibilities as fseek() gives.
661          *
662          * @param       $offset         Offset to seek to (or used as "base" for other seeks)
663          * @param       $whence         Added to offset (default: only use offset to seek to)
664          * @return      $status         Status of file seek: 0 = success, -1 = failed
665          * @throws      OutOfBoundsException    If the position is not seekable
666          */
667         public function seek (int $offset, int $whence = SEEK_SET) {
668                 // Validate parameter
669                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: offset=%d,whence=%d - CALLED!', $offset, $whence));
670                 if ($offset < 0) {
671                         // No offset is smaller than zero
672                         throw new OutOfBoundsException(sprintf('offset=%d is not valid', $offset));
673                 }
674
675                 // Call pointer instance
676                 $status = $this->getPointerInstance()->seek($offset, $whence);
677
678                 // Return status
679                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: status[%s]=%d - EXIT!', gettype($status), $status));
680                 return $status;
681         }
682
683         /**
684          * Reads given amount of bytes from file.
685          *
686          * @param       $bytes  Amount of bytes to read
687          * @return      $data   Data read from file
688          * @throws      OutOfBoundsException    If the position is not seekable
689          */
690         public function read (int $bytes = 0) {
691                 // Validate parameter
692                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: bytes=%d - CALLED!', $bytes));
693                 if ($bytes < 0) {
694                         // Throw exception
695                         throw new OutOfBoundsException(sprintf('bytes=%d is not valid', $bytes));
696                 }
697
698                 // Call pointer instance
699                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: Invoking this->pointerInstance->read(%d) ...', $bytes));
700                 $data = $this->getPointerInstance()->read($bytes);
701
702                 // Update seek position
703                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: Invoking this->updateSeekPosition() ...');
704                 $this->updateSeekPosition();
705
706                 // Return data
707                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: data[%s]=%s - EXIT!', gettype($data), $data));
708                 return $data;
709         }
710
711         /**
712          * Rewinds to the beginning of the file
713          *
714          * @return      void
715          */
716         public function rewind () {
717                 // Call pointer instance
718                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: CALLED!');
719                 $this->getPointerInstance()->rewind();
720
721                 // Trace message
722                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: EXIT!');
723         }
724
725         /**
726          * Analyzes entries in index file. This will count all found (and valid)
727          * entries, mark invalid as damaged and count gaps ("fragmentation"). If
728          * only gaps are found, the file is considered as "virgin" (no entries).
729          *
730          * @return      void
731          * @throws      BadMethodCallException  If this method is called but file is not initialized
732          */
733         public function analyzeFileStructure () {
734                 // Make sure the file is initialized
735                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: CALLED!');
736                 if (!$this->isFileInitialized()) {
737                         // Bad method call
738                         throw new BadMethodCallException('Method called but file is not initialized.', FrameworkInterface::EXCEPTION_BAD_METHOD_CALL);
739                 }
740
741                 // Init counters and gaps array
742                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: Invoking this->initCounterGapsArrays() ...');
743                 $this->initCountersGapsArray();
744
745                 // Output message (as this may take some time)
746                 self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('Analyzing file structure ... (this may take some time)'));
747
748                 // First Seek to right after file header
749                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: Invoking this->seek(%d) ...', $this->getHeaderSize() + 1));
750                 $this->seek($this->getHeaderSize() + 1);
751
752                 // Then try to load all entries
753                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: this->seekPosition=%d, looping through file ...', $this->getSeekPosition()));
754                 while ($this->isValid()) {
755                         // Get current entry
756                         $current = $this->getCurrentBlock();
757
758                         // Go to next entry
759                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: current=%s, Invoking this->readNextBlock() ...', $current));
760                         $this->readNextBlock();
761
762                         /*
763                          * If the block is empty, maybe the whole file is? This could mean
764                          * that the file has been pre-allocated.
765                          */
766                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: current(%d)[]=%s', strlen($current), gettype($current)));
767                         if (empty(trim($current, chr(0)))) {
768                                 // Then skip this part
769                                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: current[]=%s is empty - CONTINUE!', gettype($current)));
770                                 continue;
771                         }
772
773                         // Handle current record
774                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: current(%d)[%s]=%s', strlen($current), gettype($current), $current));
775                 }
776
777                 // If the last read block is empty, check gaps
778                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: current()=%d', strlen($current)));
779                 if (empty(trim($current, chr(0)))) {
780                         // Output message
781                         self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: Found a total of %d gaps.', count($this->gaps)));
782
783                         // Check gaps, if the whole file is empty.
784                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: Invoking this->isFileGapsOnly() ...');
785                         if ($this->isFileGapsOnly()) {
786                                 // Only gaps, so don't continue here.
787                                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage('BASE-BINARY-FILE: File is gaps-only - EXIT!');
788                                 return;
789                         }
790                 }
791
792                 /*
793                  * The above call has calculated a total size of all gaps. If the
794                  * percentage of gaps passes a "soft" limit and last
795                  * defragmentation is to far in the past, or if a "hard" limit has
796                  * reached, run defragmentation.
797                  */
798                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: Invoking this->isDefragmentationNeeded() ...');
799                 if ($this->isDefragmentationNeeded()) {
800                         // Run "defragmentation"
801                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: Invoking this->doRunDefragmentation() ...');
802                         $this->doRunDefragmentation();
803                 }
804
805                 // Trace message
806                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: EXIT!');
807         }
808
809         /**
810          * Reads next "block" of given bytes into $currentBlock field. THis method
811          * loads the whole file into memory when the file is just freshly
812          * initialized (only zeros in it).
813          *
814          * @return      void
815          * @throws      InvalidArgumentException        If a parameter is not valid
816          */
817         protected function readNextBlockByLength (int $length) {
818                 // Validate parameter
819                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: this->seekPosition=%d,length=%d - CALLED!', $this->getSeekPosition(), $length));
820                 if ($length < 1) {
821                         // Throw IAE
822                         throw new InvalidArgumentException(sprintf('length=%d is not valid', $length), FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
823                 }
824
825                 // Read possibly back-buffered bytes from previous call of next().
826                 $data = $this->getBackBuffer();
827
828                 /*
829                  * Read until a entry/block separator has been found. The next read
830                  * "block" may not fit, so this loop will continue until the EOB or EOF
831                  * has been reached whatever comes first.
832                  */
833                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: this->seekPosition=%d,data()=%d', $this->getSeekPosition(), strlen($data)));
834                 while ((!$this->isEndOfFileReached()) && (empty($data) || !self::isBlockSeparatorFound($data))) {
835                         // Then read the next possible block
836                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: this->seekPosition=%d, Invoking this->read(%d) ...', $this->getSeekPosition(), $length));
837                         $block = $this->read($length);
838
839                         // Is the block empty?
840                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: block()=%d,length=%d', strlen($block), $length));
841                         if (strlen($block) == 0) {
842                                 // Read empty block, maybe EOF
843                                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: this->seekPosition=%d, block is empty, maybe EOF reached - BREAK!', $this->getSeekPosition()));
844                                 break;
845                         } elseif (empty(trim($block, chr(0)))) {
846                                 // Mark it as empty
847                                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: this->seekPosition=%d, Invoking this->markCurrentBlockAsEmpty(%d) ...', $this->getSeekPosition(), $length));
848                                 $this->markCurrentBlockAsEmpty($length);
849                         }
850
851                         // At this block then
852                         $data .= $block;
853
854                         // Debug message
855                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: this->seekPosition=%d,data()=%d', $this->getSeekPosition(), strlen($data)));
856                 }
857
858                 /*
859                  * Init back-buffer which is the data that has been found beyond the
860                  * separator.
861                  */
862                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: Invoking this->initBackBuffer(), clearing this->currentBlock ...');
863                 $this->initBackBuffer();
864                 $this->setCurrentBlock('');
865
866                 // Is $data empty?
867                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: data(%d)=%s', strlen($data), $data));
868                 if (empty(trim($data, chr(0)))) {
869                         // Yes, maybe whole file was ...
870                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: this->seekPosition=%d, maybe empty file found - EXIT!', $this->getSeekPosition()));
871                         return;
872                 }
873
874                 // Separate data
875                 $dataArray = explode(chr(BinaryFile::SEPARATOR_ENTRIES), $data);
876
877                 // Left part is the actual block, right one the back-buffer data, if found
878                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: dataArray()=%d', count($dataArray)));
879                 //* PRINTR-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: dataArray=%s', print_r($dataArray, true)));
880                 $this->setCurrentBlock($dataArray[0]);
881
882                 // Is back buffere data found?
883                 if (isset($dataArray[1]) && !empty(trim($dataArray[1], chr(0)))) {
884                         // Set back buffer
885                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: Setting this->backBuffer=%s ...', $dataArray[1]));
886                         $this->setBackBuffer($dataArray[1]);
887                 }
888
889                 // Trace message
890                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: EXIT!');
891         }
892
893         /**
894          * Pre-allocates file (if enabled) with some space for later faster write access.
895          *
896          * @param       $type   Type of the file
897          * @return      void
898          * @throws      InvalidArgumentException        If a parameter is empty
899          */
900         protected function preAllocateFileByTypeLength (string $type, int $minimumBlockLength) {
901                 // Is it enabled?
902                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: type=%s,minimumBlockLength=%d - CALLED!', $type, $minimumBlockLength));
903                 if (empty($type)) {
904                         // Empty type
905                         throw new InvalidArgumentException('Parameter "type" is empty', FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
906                 } elseif ($minimumBlockLength < 1) {
907                         // Invalid block length
908                         throw new InvalidArgumentException(sprintf('Parameter minimumBlockLength=%d is not valid', $minimumBlockLength), FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
909                 } elseif (!FrameworkBootstrap::getConfigurationInstance()->isEnabled($type . '_pre_allocate')) {
910                         // Don't continue here.
911                         self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: Not pre-allocating file. - EXIT!');
912                         return;
913                 }
914
915                 // Get file size
916                 $fileSize = $this->getFileSize();
917
918                 // Calulcate seek position
919                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: minimumBlockLength=%d,fileSize=%d', $minimumBlockLength, $fileSize));
920                 $seekPosition = $this->getHeaderSize() + $minimumBlockLength * FrameworkBootstrap::getConfigurationInstance()->getConfigEntry($type . '_pre_allocate_count') + $fileSize ;
921
922                 // Now simply write a NUL there. This will pre-allocate the file.
923                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: Invoking this->writeAtPosition(%d,NUL) ...', $seekPosition));
924                 $this->writeAtPosition($seekPosition, chr(0));
925
926                 // Is the seek position zero?
927                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: fileSize=%d', $fileSize));
928                 if ($fileSize == 0) {
929                         // Mark file as gaps-only
930                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: Invoking this->markGapsOnly(%s,%d) ...', $type, $minimumBlockLength));
931                         $this->markFileGapsOnly($type, $minimumBlockLength);
932                 } else {
933                         // Analyze file structure
934                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: Invoking this->analyzeFileStructure() ...');
935                         $this->analyzeFileStructure();
936                 }
937
938                 // Rewind seek position
939                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: Invoking this->rewind() ...');
940                 $this->rewind();
941
942                 // Trace message
943                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage('BASE-BINARY-FILE: EXIT!');
944         }
945
946         /**
947          * Checks wether the current entry is valid (not at the end of the file).
948          * This method will return true if an emptied (nulled) entry has been found.
949          *
950          * @return      $isValid        Whether the next entry is valid
951          * @throws      InvalidArgumentException        If a parameter is not valid
952          */
953         protected function isValidByLength (int $length) {
954                 // Validate parameter
955                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: length=%d - CALLED!', $length));
956                 if ($length < 1) {
957                         // Throw IAE
958                         throw new InvalidArgumentException(sprintf('Parameter length=%d is not valid', $length), FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
959                 }
960
961                 // Get current seek position
962                 $seekPosition = $this->determineSeekPosition();
963
964                 // Then try to read it
965                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: seekPosition=%d', $seekPosition));
966                 $data = $this->read($length);
967
968                 // If some bytes could be read, all is fine
969                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: data[%s]()=%d', gettype($data), strlen($data)));
970                 $isValid = ((is_string($data)) && (strlen($data) > 0));
971
972                 // Get header size
973                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: isValid=%d', intval($isValid)));
974                 $headerSize = $this->getHeaderSize();
975
976                 // Is the seek position at or beyond the header?
977                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->debugMessage(sprintf('BASE-BINARY-FILE: seekPosition=%d,headerSize=%d', $seekPosition, $headerSize));
978                 if ($seekPosition >= $headerSize) {
979                         // Seek back to old position
980                         $isValid = ($isValid && $this->seek($seekPosition) === 0);
981                 } else {
982                         // Seek directly behind the header
983                         $isValid = ($isValid && $this->seek($headerSize) === 0);
984                 }
985
986                 // Return result
987                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: isValid=%d - EXIT!', intval($isValid)));
988                 return $isValid;
989         }
990
991         /**
992          * Reads next "block" of bytes into $currentBlock field. THis method loads
993          * the whole file into memory when the file is just freshly initialized
994          * (only zeros in it).
995          *
996          * @return      void
997          */
998         protected abstract function readNextBlock ();
999
1000         /**
1001          * Reads the file header
1002          *
1003          * @return      void
1004          * @throws      LogicException  If both instances are not set
1005          */
1006         public abstract function readFileHeader ();
1007
1008         /**
1009          * Searches for next suitable gap the given length of data can fit in
1010          * including padding bytes.
1011          *
1012          * @param       $length                 Length of raw data
1013          * @return      $seekPosition   Found next gap's seek position
1014          * @throws      InvalidArgumentException        If the parameter is not valid
1015          */
1016         public function searchNextGap (int $length) {
1017                 // If the file is only gaps, no need to seek
1018                 //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: length=%d - CALLED!', $length));
1019                 if ($length <= 0) {
1020                         // Throw IAE
1021                         throw new InvalidArgumentException(sprintf('length=%d is not valid', $length), FrameworkInterface::EXCEPTION_INVALID_ARGUMENT);
1022                 } elseif ($this->isFileGapsOnly()) {
1023                         /*
1024                          * The first empty block is the 2nd one right after the header, so
1025                          * one byte gap to the header.
1026                          */
1027                         $seekPosition = ($this->getHeaderSize() + 2);
1028
1029                         // Return position
1030                         //* NOISY-DEBUG: */ self::createDebugInstance(__CLASS__, __LINE__)->traceMessage(sprintf('BASE-BINARY-FILE: seekPosition=%d - EXIT!', $seekPosition));
1031                         return $seekPosition;
1032                 }
1033
1034                 // @TODO Unfinished
1035                 DebugMiddleware::getSelfInstance()->partialStub('length=' . $length);
1036         }
1037
1038 }